API Test Coverage Handoff¶
Branch: test/api-coverage-phase1
Summary¶
This document tracks the API test coverage expansion work across two commits on this branch.
What was tested (this session)¶
Bug fixed¶
tests/test_portfolio_endpoints.py: The two "preview" tests called/api/experience/previewwhich does not exist (returns 422 because "preview" is not a valid integer for the{index}path param). Fixed to use/api/experience?top=4which is the actual supported syntax.
New test files added¶
tests/test_settings_and_core.py — Settings class + OpenAPI schema (14 tests)
Settingsinstantiation with defaults (isolated from.envvia_env_file=None)- Settings reads env vars correctly (email, enable_cv_generation, api_admin_token, use_managed_identity)
- Singleton identity (
settings is settings) custom_openapischema: correct title/version,serversblock,x-uigen-idinjected on/api/projects,/api/experience,/api/feature-flagsGET /api/download-cvhidden from OpenAPI schema (include_in_schema=False)- Schema is cached after first call
/openapi.jsonHTTP endpoint returns 200 with correct content-type and paths
tests/test_uigen_router.py — UIGen token endpoint (9 tests)
- Guest token issued when no Authorization header
- Each guest call returns a unique token
is_valid_guest_tokenaccepts newly issued tokens- Unknown tokens and expired tokens rejected
_cleanup_expired()removes stale tokens from in-memory store- Admin token echoed back for valid admin bearer
- Invalid admin bearer → 401
- Unconfigured admin token (empty
API_ADMIN_TOKEN) → 401
tests/test_contact_validation.py — Contact form + PATCH /api/download-cv (17 tests)
- Name too short (< 2 chars), at min (2), at max (100), over max (> 100)
- Name whitespace stripping: " AB " passes; " " (whitespace only) fails after strip
- Message too short (< 10 chars), at min (10), over max (> 5000)
- Company over max (> 200 chars), at max (200)
- Non-string name rejected in strict mode
PATCH /api/download-cv: English, Nederlands, file-missing → 404, default-to-English body
tests/test_feature_flags_admin.py — PATCH /api/feature-flags + feature_flags.set_flag (10 tests)
PATCH /api/feature-flagsrequires auth (no token → 403/401, wrong token → 401)- Empty body → 400 ("No flags provided")
- No App Config client → 503
- Happy path with mocked Azure client for cvGeneration and portfolioContentApi
set_flagraises RuntimeError when client not initialisedset_flagcallsset_configuration_settingand updates local cacheportfolio-content-apienv var fallback (FEATURE_PORTFOLIO_CONTENT_API=true)portfolio-content-apidefaults to False
tests/test_admin_mock.py — Admin write endpoints without real DB (22 tests)
- Auth checks: no token → 401/403, wrong token → 401
_require_db→ 503 whenget_dbyields None (projects, experience/reorder)- Schema validation failures on malformed payloads
PATCH /api/experience/reorderinvalid UUID → 422PATCH /api/experience/reordermissing UUID → 404PATCH /api/projects/reordermissing slug → 404PUT /api/projects/{slug}missing → 404PUT /api/experience/{id}missing → 404- Skill category:
PUT,PATCH,DELETE→ 404 when not found - Individual skill:
PATCH,PUT,DELETE→ 404 when not found POST /api/skills/categories/{id}/skills→ 404 when category not foundGET /api/experience?top=2returns exactly 2 entries (static fallback)GET /api/experience(no top) returns all entries (static fallback)
Test count before / after¶
| Session | Passing tests |
|---|---|
| Start of this session | 90 (on main after PR #370) |
| After this session | 161 |
| Net new | +71 |
Remaining gaps¶
The following areas still have limited or no coverage. They are non-trivial to test without a live DB or specific runtime dependencies:
api/core/logging.pyandapi/core/exceptions.py— currently empty stub files; nothing to test yet.api/repositories/andapi/services/— skeleton packages only; no logic to test yet.api/routers/admin.py— PUT (replace) happy paths for projects/experience/skills — only 404 error paths are covered without a real DB. The happy path tests exist intest_admin_endpoints.pywhich requires PostgreSQL.api/main.py—GET /api/cv/generatehappy path — the GET variant streams a PDFFileResponse; only the PATCH variant is covered in the happy path test. The GET path calls_run_cv_generationidentically, so the core logic is exercised, but the FileResponse serialisation is not.api/feature_flags.py—_refresh()with live Azure SDK — currently mocked. Integration test would require a real App Configuration instance.- Rate limiting on non-contact endpoints — projects/experience/skills endpoints are rate-limited at 60/minute. Only the contact limit (5/min) is tested.
- CORS headers — no tests assert the
Access-Control-Allow-Originheader behaviour. api/main.py—send_email_notificationexception path — the outerexcept Exceptioninsend_email_notificationis exercised via thetest_email_auth_error_does_not_fail_requesttest, but the general exception path (non-SMTPAuthenticationError) is not.
Decisions made¶
/api/experience/previewremoved from test_portfolio_endpoints.py: That route does not exist. The correct endpoint for a preview list isGET /api/experience?top=4.- azure SDK stubs: The
azure-appconfigurationpackage is not installed in the dev environment. Tests that exerciseset_flagor the PATCH feature-flags happy path inject lightweightsys.modulesstubs to allow the lazy import inside the production code to resolve. - Settings
.envisolation:test_settings_defaults_are_safepasses_env_file=Noneto the Settings constructor to bypass the repo's.env(which setsENABLE_CV_GENERATION=true), allowing the test to assert on pure default values. - Static vs mock DB for
topparameter: Thetopquery parameter is handled in the static fallback path (if db_engine.AsyncSessionLocal is None). The mock DB session (autouse fixture) bypasses that path. Tests fortoptherefore temporarily setAsyncSessionLocal = Noneto force the static code path.