Skip to content

Fix API bugs surfaced by live tracing (422/400 instead of 500) - #2455

Open
Vinv-AI wants to merge 1 commit into
fastapi:masterfrom
VinvAI:fix-live-trace-bugs
Open

Fix API bugs surfaced by live tracing (422/400 instead of 500)#2455
Vinv-AI wants to merge 1 commit into
fastapi:masterfrom
VinvAI:fix-live-trace-bugs

Conversation

@Vinv-AI

@Vinv-AI Vinv-AI commented Aug 22, 2026

Copy link
Copy Markdown

Pull Request

Discussion: #2454

Description

Several endpoints returned 500 (or crashed) on inputs that should be rejected
with a 4xx. This PR fixes them and adds tests for each case.

  • Pagination (items, users): skip/limit were unbounded, so negative
    values reached Postgres OFFSET/LIMIT and 500'd. Added shared SkipQuery
    (ge=0) and LimitQuery (ge=1, le=100) in app/api/params.py; out-of-range
    values now return 422.
  • Private user create (dev-only /private router): email was a bare str,
    so an invalid address was committed and every later read 500'd on UserPublic
    (EmailStr). Now uses EmailStr (max_length=255) and password bounds
    (8–128) → 422 before insert; drops the accepted-but-ignored is_verified
    (no such column).
  • Private user create: a duplicate email raised an unhandled UniqueViolation
    → 500. Now looks up first and returns 400; creates via crud.create_user.
  • Password recovery HTML endpoint: the response header name was "subject:",
    an invalid HTTP header name that raised RuntimeError. Now "subject".
  • Password recovery: the email is sent via BackgroundTasks so the HTTP
    response no longer waits on SMTP (the enumeration-safe uniform response is
    unchanged).

No public/documented API changes. B1–B3 are on the private router, which is
only mounted when FASTAPI_ENV == "development" (E2E/local).

AI Disclaimer

Models: Composer 2.5 (via Cursor + VinvAI)

Prompt (paraphrased): apply the live-trace fix set (unbounded pagination → 422,
private-route email/password validation and duplicate handling, the invalid
subject: recovery header, and background-task SMTP) — verifying each bug
against the current code first — and add a test for each case.

AI transcript
  • Verified each bug against the current code (private.py bare email: str +
    no dup check; users.py/items.py unbounded skip/limit; login.py
    "subject:" header and synchronous send_email; confirmed is_verified has
    no model column and the private router is dev-only).
  • Implemented: app/api/params.py (SkipQuery/LimitQuery); users.py /
    items.py use them; private.py (EmailStr + bounds, duplicate → 400,
    crud.create_user); login.py (subject header, BackgroundTasks).
  • Added tests: invalid email, short password, duplicate email (private);
    negative skip (users); negative limit (items); valid recovery header,
    background-vs-no-op email (login).
  • Dropped a proposed change to the superuser-only recovery-HTML 404 (no real
    enumeration benefit; returning a preview for a non-existent user is wrong).
  • Verified locally: all changed files compile; ruff check and ruff format
    are clean. The pytest suite was NOT run locally (no Postgres available in this
    environment) — see checklist note.

Checklist

  • This PR links to a GitHub Discussion for the proposed code change.
  • I added tests for the change.
  • The new or updated tests fail on the main branch and pass on this PR.
  • Coverage stays at 100%.
  • The documentation explains the change if needed.

Several endpoints returned 500 (or crashed) on inputs that should be
rejected with a 4xx. Fixes:

- Pagination (items, users): skip/limit were unbounded, so negative values
  reached Postgres OFFSET/LIMIT and 500'd. Add shared SkipQuery (ge=0) and
  LimitQuery (ge=1, le=100) in app/api/params.py; out-of-range -> 422.
- Private user create (dev-only /private router): email was a bare str, so an
  invalid address was committed and every later read 500'd on UserPublic
  (EmailStr). Use EmailStr (max_length=255) and password bounds (8-128) -> 422
  before insert; drop the accepted-but-ignored is_verified (no column).
- Private user create: duplicate email raised an unhandled UniqueViolation ->
  500. Look up first and return 400; create via crud.create_user.
- Password recovery HTML endpoint: response header name was "subject:", an
  invalid HTTP header name that raised RuntimeError. Use "subject".
- Password recovery: send the email via BackgroundTasks so the HTTP response
  no longer waits on SMTP (enumeration-safe uniform response unchanged).

Tests added for each case (invalid email, short password, duplicate email,
negative skip, negative limit, valid recovery header, background/no-op email).

Co-Authored-By: Vinv-AI <309466812+Vinv-AI@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants