New tests tests/test_openapi_security.py verify presence of bearerAuth and 403s.

This commit is contained in:
jableader 2025-11-01 14:01:48 +11:00
parent 4470289b61
commit 538538d909
3 changed files with 30 additions and 5 deletions

View file

@ -52,6 +52,16 @@ def extend_with_problem_and_cookie_auth(app: FastAPI) -> None:
}, },
}, },
) )
responses.setdefault(
"Problem403",
{
"description": "Forbidden",
"content": {
"application/problem+json": {},
"application/json": {"schema": {"$ref": "#/components/schemas/ProblemDetails"}},
},
},
)
# Cookie-based auth for v1 documentation (does not enforce at runtime) # Cookie-based auth for v1 documentation (does not enforce at runtime)
security_schemes.setdefault( security_schemes.setdefault(
@ -151,7 +161,7 @@ def extend_with_problem_and_cookie_auth(app: FastAPI) -> None:
if isinstance(store, dict) and store.get("$ref") == "#/components/schemas/StoreEnum": if isinstance(store, dict) and store.get("$ref") == "#/components/schemas/StoreEnum":
props["storeName"] = {"$ref": "#/components/schemas/StoreNameOut"} props["storeName"] = {"$ref": "#/components/schemas/StoreNameOut"}
# Mark bearer security for v2 routes we know require auth # Mark bearer security for v2 routes we know require auth
# Simple heuristic: underline select paths under /api/v1/users/me and /api/v1/households/* that are protected # Simple heuristic: underline select paths under /api/v1/users/me and /api/v1/households/* that are protected
for path, ops in paths.items(): for path, ops in paths.items():
if not isinstance(path, str) or not path.startswith("/api/v1/"): if not isinstance(path, str) or not path.startswith("/api/v1/"):
@ -170,6 +180,10 @@ def extend_with_problem_and_cookie_auth(app: FastAPI) -> None:
security = op.setdefault("security", []) security = op.setdefault("security", [])
if not any(isinstance(s, dict) and "bearerAuth" in s for s in security): if not any(isinstance(s, dict) and "bearerAuth" in s for s in security):
security.append({"bearerAuth": []}) security.append({"bearerAuth": []})
# ensure 403 Problem is defined on these operations
resp = op.setdefault("responses", {})
if "403" not in resp:
resp["403"] = {"$ref": "#/components/responses/Problem403"}
return spec return spec

View file

@ -235,10 +235,12 @@ Impact on existing routes (exact files to refactor):
- **Acceptance**: Admins can invite by email; invite accept adds user to household; listing households shows membership with roles. - **Acceptance**: Admins can invite by email; invite accept adds user to household; listing households shows membership with roles.
5. **[ ] Update OpenAPI Specification**: 5. **[ ] Update OpenAPI Specification**:
- Modify the script `scripts/export_openapi.py` to correctly generate the new specification, or manually update `openapi.json`. This is crucial for the frontend team. - ✅ Augmentation updated in `api/openapi.py`:
- Replace cookie auth doc with JWT bearer auth. Keep RFC7807 components. Ensure shopping outward enum uses `home|coles|woolworths`. - Adds `bearerAuth` security scheme while keeping `cookieAuth` for v1.
- Add household path parameter and `403` responses where applicable. - Marks household routes and `/users/me/*` with bearer security and adds `403` Problem response.
- Ensure all array properties are present (even if empty) as in v1. - Preserves RFC7807 Problem responses and shopping storeName outward enum normalization.
- ✅ New tests `tests/test_openapi_security.py` verify presence of bearerAuth and 403s.
- ⏳ Export script already writes `openapi.json`; once JWT is fully in place and routes are moved under household prefixes, re-run and hand off to frontend.
6. **[ ] Refactor and Test**: 6. **[ ] Refactor and Test**:
- Update `tests/` to reflect the new API structure and authentication. Tests will need to be updated to handle the `{householdSlug}` path parameter and provide a valid JWT. - Update `tests/` to reflect the new API structure and authentication. Tests will need to be updated to handle the `{householdSlug}` path parameter and provide a valid JWT.

View file

@ -20,6 +20,15 @@ class TestOpenAPISecurity(unittest.TestCase):
# Users me households # Users me households
op = paths.get("/api/v1/users/me/households", {}).get("get") op = paths.get("/api/v1/users/me/households", {}).get("get")
assert op and any("bearerAuth" in s for s in op.get("security", [])) assert op and any("bearerAuth" in s for s in op.get("security", []))
# and has 403 in responses
assert "403" in op.get("responses", {})
# Household whoami # Household whoami
op = paths.get("/api/v1/households/{householdSlug}/whoami", {}).get("get") op = paths.get("/api/v1/households/{householdSlug}/whoami", {}).get("get")
assert op and any("bearerAuth" in s for s in op.get("security", [])) assert op and any("bearerAuth" in s for s in op.get("security", []))
assert "403" in op.get("responses", {})
def test_problem_403_component_present(self):
spec = main.app.openapi()
comps = spec.get("components", {})
responses = comps.get("responses", {})
assert "Problem403" in responses