open-notebook/tests/test_cors_credentials.py
Luis Novo 8c85728de2
ci: gate PRs on mypy, start ignore_errors burn-down (#1076)
* ci: gate PRs on mypy, start ignore_errors burn-down

Add a backend-typecheck CI job running uv run python -m mypy . and bring
the repo-wide baseline from 197 errors to 0 so the gate blocks new type
errors from now on:

- enable the pydantic mypy plugin (resolves 138 false positives on
  models whose fields have Field(None, ...) defaults)
- fix the remaining errors with real annotations; the only new type:
  ignore comments cover a genuine langgraph typing limitation (partial
  state dicts are valid at runtime but the overloads require the full
  state type) and tests that intentionally pass invalid input
- start the ignore_errors burn-down: open_notebook.graphs.transformation,
  open_notebook.graphs.ask and api.routers.models are now type-checked;
  stale blocks for the deleted api.client and api.podcast_api_service
  modules removed. Only open_notebook.domain.notebook remains exempt
  (DB layer is migrating to surreal-basics)
- fix the mypy.ini header comment to describe what the config does

* test: use typing.get_args on the registry literal check

The registry test landed in parallel using Literal.__args__, which the
mypy gate in this branch rejects; align it with the get_args() idiom
used by the rest of the file.
2026-07-11 19:25:29 -03:00

116 lines
4.9 KiB
Python

"""
Tests for the CORS wildcard + allow_credentials fix (api/main.py).
Combining allow_origins=["*"] with allow_credentials=True makes Starlette's
CORSMiddleware reflect the request's Origin header verbatim instead of
returning a literal "*" (browsers reject a literal wildcard alongside
credentials) - defeating the origin allowlist for any credentialed request.
allow_credentials is now keyed on the parsed origins list: False whenever it
contains "*" (whether from the unset default or an explicit CORS_ORIGINS=*),
True once an operator scopes CORS_ORIGINS to specific origins.
"""
from typing import cast
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.testclient import TestClient
from api import main as api_main
class TestRealAppDefaultsToNoCredentialsWithWildcard:
"""The test environment has no CORS_ORIGINS set, matching the shipped
default - this validates the actual registered app, not a simulation."""
def test_default_test_environment_is_wildcard(self):
assert api_main.CORS_IS_DEFAULT_WILDCARD is True
assert api_main.CORS_ALLOWED_ORIGINS == ["*"]
assert api_main.CORS_ALLOW_CREDENTIALS is False
def test_cors_middleware_registered_with_credentials_disabled(self):
matches = [
m for m in api_main.app.user_middleware if m.cls is CORSMiddleware
]
assert len(matches) == 1
assert matches[0].kwargs["allow_credentials"] is False
def test_real_response_does_not_claim_allow_credentials(self):
client = TestClient(api_main.app)
response = client.get(
"/health", headers={"Origin": "https://evil.example.com"}
)
assert response.status_code == 200
assert "access-control-allow-credentials" not in {
k.lower() for k in response.headers.keys()
}
# Still reflects the origin (wildcard-equivalent), just without
# granting credentialed access.
assert response.headers.get("access-control-allow-origin") in ("*", "https://evil.example.com")
class TestExplicitWildcardAlsoDisablesCredentials:
"""An operator who sets CORS_ORIGINS=* explicitly must get the same
treatment as the unset default. The predicate keys on the parsed list
containing "*", not on whether the env var was set - keying on the env
var would reintroduce the exact reflect-any-Origin behavior this fix
prevents. CORS_ALLOW_CREDENTIALS is fixed at api.main import time, so
these exercise the real parser plus the same expression used there."""
def test_explicit_wildcard_disables_credentials(self):
origins = api_main._parse_cors_origins("*")
assert origins == ["*"]
assert ("*" not in origins) is False
def test_explicit_origins_enable_credentials(self):
origins = api_main._parse_cors_origins(
"https://notebook.example.com, https://other.example.com"
)
assert origins == [
"https://notebook.example.com",
"https://other.example.com",
]
assert ("*" not in origins) is True
class TestCorsHeadersHelperMatchesMiddlewarePolicy:
"""api/main.py's _cors_headers() manually builds CORS headers for error
responses (for errors raised before CORSMiddleware runs) - it must not
grant credentials the real middleware wouldn't."""
def test_omits_allow_credentials_header_for_wildcard(self, monkeypatch):
monkeypatch.setattr(api_main, "CORS_ALLOW_CREDENTIALS", False)
monkeypatch.setattr(api_main, "CORS_ALLOWED_ORIGINS", ["*"])
class FakeRequest:
headers = {"origin": "https://evil.example.com"}
# cast: _cors_headers only reads request.headers
headers = api_main._cors_headers(cast(Request, FakeRequest()))
assert "Access-Control-Allow-Credentials" not in headers
def test_includes_allow_credentials_header_for_explicit_origins(self, monkeypatch):
monkeypatch.setattr(api_main, "CORS_ALLOW_CREDENTIALS", True)
monkeypatch.setattr(
api_main, "CORS_ALLOWED_ORIGINS", ["https://notebook.example.com"]
)
class FakeRequest:
headers = {"origin": "https://notebook.example.com"}
# cast: _cors_headers only reads request.headers
headers = api_main._cors_headers(cast(Request, FakeRequest()))
assert headers["Access-Control-Allow-Credentials"] == "true"
def test_disallowed_origin_still_gets_no_allow_origin_header(self, monkeypatch):
monkeypatch.setattr(api_main, "CORS_ALLOW_CREDENTIALS", True)
monkeypatch.setattr(
api_main, "CORS_ALLOWED_ORIGINS", ["https://notebook.example.com"]
)
class FakeRequest:
headers = {"origin": "https://evil.example.com"}
# cast: _cors_headers only reads request.headers
headers = api_main._cors_headers(cast(Request, FakeRequest()))
assert "Access-Control-Allow-Origin" not in headers