fix(restapi): NoResource/NoHandler → 404, не 500

GlobalExceptionHandler catch-all Exception → 500 ловил NoResourceFoundException
и NoHandlerFoundException. Симптомы на prod:

- /swagger-ui/index.html при disabled springdoc возвращал 500 (UI всё равно
  не рендерился, но spring логировал 'Unhandled exception' на каждый запрос —
  спамил alerting и затруднял поиск настоящих ошибок).
- GET /api/v1/typo от integrator'ов давал 500 вместо 404, осложняло
  debugging без traceId-correlation.
- Любой stale frontend bundle с переименованным endpoint мог триггерить
  false-positive incident alerts.

Fix: explicit @ExceptionHandler для обоих exception типов → 404 not_found.
Логирование DEBUG (не WARN/ERROR) — это ожидаемый 404, не аномалия.

Test: SmokeE2ETest.unknownPath_returns404WithNotFoundCode покрывает оба пути
(/api/v1/* и /swagger-ui/*) → 404 + body.code='not_found'.
This commit is contained in:
Zimin A.N.
2026-05-13 00:00:36 +03:00
parent 6c48cfac46
commit f2adc37e2e
2 changed files with 68 additions and 0 deletions
@@ -127,4 +127,33 @@ class SmokeE2ETest {
assertThat(newEntries).isNotEmpty();
assertThat(newEntries.get(0).getBusinessKey()).isEqualTo(businessKey);
}
/**
* Regression: unrouted path должен возвращать 404 not_found, не 500
* internal_error.
*
* <p>До MR-fix: NoResourceFoundException попадал в generic Exception handler
* в GlobalExceptionHandler → 500. Симптомы на prod включали:
* <ul>
* <li>{@code /swagger-ui/index.html} при disabled springdoc → 500 вместо 404
* (UI не рендерился но logs спамились "Unhandled exception").</li>
* <li>Integrator'ы при typo в endpoint path получали 500 — затрудняло
* debugging без traceId correlation.</li>
* </ul>
*
* <p>Покрывает оба пути: путь похожий на API ({@code /api/v1/notexist}) и
* generic non-API ({@code /random/path}).
*/
@Test
void unknownPath_returns404WithNotFoundCode() throws Exception {
mvc.perform(get("/api/v1/this-endpoint-does-not-exist"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.code").value("not_found"));
mvc.perform(get("/swagger-ui/index.html"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.code").value("not_found"));
}
}