Skip to content

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/preview which does not exist (returns 422 because "preview" is not a valid integer for the {index} path param). Fixed to use /api/experience?top=4 which is the actual supported syntax.

New test files added

tests/test_settings_and_core.py — Settings class + OpenAPI schema (14 tests)

  • Settings instantiation with defaults (isolated from .env via _env_file=None)
  • Settings reads env vars correctly (email, enable_cv_generation, api_admin_token, use_managed_identity)
  • Singleton identity (settings is settings)
  • custom_openapi schema: correct title/version, servers block, x-uigen-id injected on /api/projects, /api/experience, /api/feature-flags
  • GET /api/download-cv hidden from OpenAPI schema (include_in_schema=False)
  • Schema is cached after first call
  • /openapi.json HTTP 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_token accepts 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-flags requires 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_flag raises RuntimeError when client not initialised
  • set_flag calls set_configuration_setting and updates local cache
  • portfolio-content-api env var fallback (FEATURE_PORTFOLIO_CONTENT_API=true)
  • portfolio-content-api defaults 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 when get_db yields None (projects, experience/reorder)
  • Schema validation failures on malformed payloads
  • PATCH /api/experience/reorder invalid UUID → 422
  • PATCH /api/experience/reorder missing UUID → 404
  • PATCH /api/projects/reorder missing slug → 404
  • PUT /api/projects/{slug} missing → 404
  • PUT /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 found
  • GET /api/experience?top=2 returns 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:

  1. api/core/logging.py and api/core/exceptions.py — currently empty stub files; nothing to test yet.
  2. api/repositories/ and api/services/ — skeleton packages only; no logic to test yet.
  3. 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 in test_admin_endpoints.py which requires PostgreSQL.
  4. api/main.pyGET /api/cv/generate happy path — the GET variant streams a PDF FileResponse; only the PATCH variant is covered in the happy path test. The GET path calls _run_cv_generation identically, so the core logic is exercised, but the FileResponse serialisation is not.
  5. api/feature_flags.py_refresh() with live Azure SDK — currently mocked. Integration test would require a real App Configuration instance.
  6. Rate limiting on non-contact endpoints — projects/experience/skills endpoints are rate-limited at 60/minute. Only the contact limit (5/min) is tested.
  7. CORS headers — no tests assert the Access-Control-Allow-Origin header behaviour.
  8. api/main.pysend_email_notification exception path — the outer except Exception in send_email_notification is exercised via the test_email_auth_error_does_not_fail_request test, but the general exception path (non-SMTPAuthenticationError) is not.

Decisions made

  • /api/experience/preview removed from test_portfolio_endpoints.py: That route does not exist. The correct endpoint for a preview list is GET /api/experience?top=4.
  • azure SDK stubs: The azure-appconfiguration package is not installed in the dev environment. Tests that exercise set_flag or the PATCH feature-flags happy path inject lightweight sys.modules stubs to allow the lazy import inside the production code to resolve.
  • Settings .env isolation: test_settings_defaults_are_safe passes _env_file=None to the Settings constructor to bypass the repo's .env (which sets ENABLE_CV_GENERATION=true), allowing the test to assert on pure default values.
  • Static vs mock DB for top parameter: The top query parameter is handled in the static fallback path (if db_engine.AsyncSessionLocal is None). The mock DB session (autouse fixture) bypasses that path. Tests for top therefore temporarily set AsyncSessionLocal = None to force the static code path.