From 91908b8ecaf5fb8d62619035d2c3badc22badb41 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 17:20:44 +0000 Subject: [PATCH] test(cdk): fail synth when a route has no authorizer Synthesize ExclusaoClienteStack and reject any HttpApi route with no authorizer, except GET /health. Install the CDK library in the CI test job so the gate runs there. Co-authored-by: Rodrigo Patote --- .github/workflows/ci.yml | 11 +++++- tests/test_cdk_route_authorizer.py | 60 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/test_cdk_route_authorizer.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 604fae3..6cd69ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,9 +46,18 @@ jobs: with: python-version: "3.12" cache: pip + cache-dependency-path: | + requirements.txt + cdk/requirements.txt + + - uses: actions/setup-node@v4 + with: + node-version: "20" - name: Install dependencies - run: pip install -r requirements.txt + run: | + pip install -r requirements.txt + pip install -r cdk/requirements.txt - name: Run pytest # moto mocks DynamoDB — no AWS credentials required diff --git a/tests/test_cdk_route_authorizer.py b/tests/test_cdk_route_authorizer.py new file mode 100644 index 0000000..d52516a --- /dev/null +++ b/tests/test_cdk_route_authorizer.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +import importlib.util +import json +import os +from collections.abc import Mapping +from pathlib import Path +from typing import Any + +from aws_cdk import App +from aws_cdk.assertions import Template + +ROOT = Path(__file__).resolve().parents[1] +_PUBLIC_ROUTE_KEY = "GET /health" +_AUTHORIZER_TYPES = frozenset({"AWS_IAM", "CUSTOM", "JWT"}) + + +def _synthesized_routes() -> Mapping[str, Mapping[str, Any]]: + spec = importlib.util.spec_from_file_location( + "exclusao_cdk_app", + ROOT / "cdk" / "app.py", + ) + if spec is None or spec.loader is None: + raise RuntimeError("failed to load cdk/app.py") + module = importlib.util.module_from_spec(spec) + previous = Path.cwd() + os.chdir(ROOT) + try: + spec.loader.exec_module(module) + context = json.loads((ROOT / "cdk.json").read_text(encoding="utf-8")).get( + "context", {} + ) + app = App(context=context) + stack = module.ExclusaoClienteStack(app, "ExclusaoClienteStack") + template = Template.from_stack(stack) + finally: + os.chdir(previous) + return template.find_resources("AWS::ApiGatewayV2::Route") + + +def test_every_http_route_has_authorizer_except_get_health() -> None: + routes = _synthesized_routes() + assert routes, "CDK synth produced no AWS::ApiGatewayV2::Route resources" + + offenders: list[str] = [] + for logical_id, resource in sorted(routes.items()): + props = resource.get("Properties", {}) + route_key = props.get("RouteKey") + if route_key == _PUBLIC_ROUTE_KEY: + continue + auth_type = props.get("AuthorizationType") + missing_id = auth_type in {"CUSTOM", "JWT"} and "AuthorizerId" not in props + if auth_type not in _AUTHORIZER_TYPES or missing_id: + shown = route_key if isinstance(route_key, str) else "" + offenders.append(f"{logical_id}: {shown} AuthorizationType={auth_type!r}") + + assert not offenders, ( + "Every API route needs an authorizer except GET /health. Offenders:\n" + + "\n".join(offenders) + )