fix(mera/b2c): выключенная ручка расчёта подтверждала своё существование и печатала схему #3233

Merged
bot-backend merged 1 commit from fix/disabled-endpoint-leaks-schema into main 2026-08-29 15:31:27 +00:00
Collaborator

Найдено прод-проверкой сразу после мержа #3230 — не тестами, а живым запросом к выключенному контуру.

Что обещал докстринг

Отвечаем 404, а не 403: выключенная ручка не должна подтверждать, что она существует.

Что было на самом деле (замер, poincare, 29.08.2026, PUBLIC_ESTIMATE_ENABLED=false)

POST /api/public/mera/estimate  {}          → 422
POST /api/public/mera/estimate  {валидное}  → 404
POST /api/public/mera/nosuchthing           → 401   ← так выглядит несуществующий путь

Тело ответа на 422:

{"detail":[{"type":"missing","loc":["body","address"],...},
           {"type":"missing","loc":["body","area_m2"],...},
           {"type":"missing","loc":["body","rooms"],...},
           {"type":"missing","loc":["body","consent"],...}]}

То есть 422 отличается и от 404, и от 401: он подтверждает, что ручка существует, и заодно печатает её схему. Обещание докстринга не выполнялось ни разу — оно выполнялось только для того входа, который автор проверял руками.

Почему так вышло

Дефект не в логике гейта, а в его месте. Проверка стояла первой строкой тела хендлера:

async def public_estimate(...):
    """..."""
    _require_public_estimate_enabled()

FastAPI валидирует тело запроса раньше, чем управление доходит до кода хендлера. При невалидном теле до строки с проверкой просто не доходило.

Починка

Гейт перенесён в dependencies=[Depends(_require_public_estimate_enabled)] обеих ручек (/estimate и /estimate/read). Зависимости решаются до разбора тела, поэтому выключенная ручка теперь неотличима от отсутствующей при любом входе.

Причина, по которой гейт обязан быть зависимостью, записана в его докстринге вместе с замером — чтобы следующий не перенёс проверку обратно в тело как «так читается лучше».

Проверка

Тест двусторонний и фальсифицирован: возврат вызова в тело хендлера красит три теста (включая уже существовавший test_estimate_read_is_404_while_flag_is_off — он проверял только валидный вход), обратная правка возвращает 17/17 зелёных.

Новый тест перебирает три формы входа — пустое тело, частичное, валидное — и требует 404 на каждой, плюс отсутствие имён полей в ответе.

Приёмка на проде после деплоя

ssh poincare "docker exec tradein-backend curl -s -o /dev/null -w '%{http_code}\n' -XPOST -H 'Content-Type: application/json' -d '{}' localhost:8000/api/public/mera/estimate"

Ожидается 404 (было 422).

Найдено прод-проверкой сразу после мержа #3230 — не тестами, а живым запросом к выключенному контуру. ## Что обещал докстринг > Отвечаем 404, а не 403: выключенная ручка не должна подтверждать, что она существует. ## Что было на самом деле (замер, poincare, 29.08.2026, `PUBLIC_ESTIMATE_ENABLED=false`) ``` POST /api/public/mera/estimate {} → 422 POST /api/public/mera/estimate {валидное} → 404 POST /api/public/mera/nosuchthing → 401 ← так выглядит несуществующий путь ``` Тело ответа на 422: ```json {"detail":[{"type":"missing","loc":["body","address"],...}, {"type":"missing","loc":["body","area_m2"],...}, {"type":"missing","loc":["body","rooms"],...}, {"type":"missing","loc":["body","consent"],...}]} ``` То есть **422 отличается и от 404, и от 401**: он подтверждает, что ручка существует, и заодно печатает её схему. Обещание докстринга не выполнялось ни разу — оно выполнялось только для того входа, который автор проверял руками. ## Почему так вышло Дефект не в логике гейта, а в его **месте**. Проверка стояла первой строкой тела хендлера: ```python async def public_estimate(...): """...""" _require_public_estimate_enabled() ``` FastAPI валидирует тело запроса раньше, чем управление доходит до кода хендлера. При невалидном теле до строки с проверкой просто не доходило. ## Починка Гейт перенесён в `dependencies=[Depends(_require_public_estimate_enabled)]` обеих ручек (`/estimate` и `/estimate/read`). Зависимости решаются **до** разбора тела, поэтому выключенная ручка теперь неотличима от отсутствующей при любом входе. Причина, по которой гейт обязан быть зависимостью, записана в его докстринге вместе с замером — чтобы следующий не перенёс проверку обратно в тело как «так читается лучше». ## Проверка Тест двусторонний и **фальсифицирован**: возврат вызова в тело хендлера красит три теста (включая уже существовавший `test_estimate_read_is_404_while_flag_is_off` — он проверял только валидный вход), обратная правка возвращает 17/17 зелёных. Новый тест перебирает три формы входа — пустое тело, частичное, валидное — и требует 404 на каждой, плюс отсутствие имён полей в ответе. ## Приёмка на проде после деплоя ```bash ssh poincare "docker exec tradein-backend curl -s -o /dev/null -w '%{http_code}\n' -XPOST -H 'Content-Type: application/json' -d '{}' localhost:8000/api/public/mera/estimate" ``` Ожидается **404** (было 422).
bot-backend added 1 commit 2026-08-29 15:19:43 +00:00
fix(mera/b2c): выключенная ручка расчёта подтверждала своё существование и печатала схему
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m10s
6ab13649f6
Докстринг обещал: «Отвечаем 404, а не 403: выключенная ручка не должна
подтверждать, что она существует». Замер на проде 29.08.2026 (флаг выключен):

  POST /api/public/mera/estimate  {}          → 422 + address/area_m2/rooms/consent
  POST /api/public/mera/estimate  {валидное}  → 404
  POST /api/public/mera/nosuchthing           → 401 (rbac)

422 отличается и от 404, и от 401 — то есть подтверждает, что ручка есть, и
заодно выдаёт её схему.

Причина не в логике гейта, а в его МЕСТЕ: проверка стояла первой строкой тела
хендлера, а FastAPI валидирует тело раньше, чем доходит до кода. Гейт перенесён
в dependencies=[Depends(...)] обеих ручек — зависимости решаются до разбора тела,
и выключенная ручка неотличима от отсутствующей при любом входе.

Тест двусторонний и фальсифицирован: возврат вызова в тело красит три теста
(включая уже существовавший про 404), обратная правка — снова зелено.
bot-backend merged commit 3b029cc0c7 into main 2026-08-29 15:31:27 +00:00
bot-backend deleted branch fix/disabled-endpoint-leaks-schema 2026-08-29 15:31:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3233
No description provided.