create-caspian-app 1.6.3-alpha → 1.6.4-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tests/test_health_route.py +85 -42
- package/package.json +1 -1
|
@@ -1,42 +1,85 @@
|
|
|
1
|
-
"""Integration test for the app's HTTP surface via httpx2's ASGI transport.
|
|
2
|
-
|
|
3
|
-
`/health` is the simplest always-on route and exercises the full middleware
|
|
4
|
-
stack (session, CSRF, security headers, auth) end to end. We drive the ASGI
|
|
5
|
-
app directly with httpx2's AsyncClient over its ASGITransport rather than
|
|
6
|
-
Starlette's TestClient.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
"""Integration test for the app's HTTP surface via httpx2's ASGI transport.
|
|
2
|
+
|
|
3
|
+
`/health` is the simplest always-on route and exercises the full middleware
|
|
4
|
+
stack (session, CSRF, security headers, auth) end to end. We drive the ASGI
|
|
5
|
+
app directly with httpx2's AsyncClient over its ASGITransport rather than
|
|
6
|
+
Starlette's TestClient.
|
|
7
|
+
|
|
8
|
+
Route expectations are derived from the app's own auth policy rather than
|
|
9
|
+
hard-coded, because `is_all_routes_private` in `src/lib/auth/auth_config.py`
|
|
10
|
+
changes what the same request does. Under public-first (`False`) an unmatched
|
|
11
|
+
path reaches the router and renders the 404 page; under all-private (`True`)
|
|
12
|
+
`AuthMiddleware` sends a signed-out visitor to the sign-in route with a `next`
|
|
13
|
+
parameter before routing ever runs. Both are correct — which one applies is a
|
|
14
|
+
configuration choice, so the test asks the configured policy instead of
|
|
15
|
+
assuming one of them.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
|
|
20
|
+
import httpx2
|
|
21
|
+
import pytest
|
|
22
|
+
from casp.auth import Auth
|
|
23
|
+
|
|
24
|
+
import main
|
|
25
|
+
|
|
26
|
+
UNKNOWN_PATH = "/definitely-not-a-real-route"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _get(path: str) -> httpx2.Response:
|
|
30
|
+
# ASGITransport speaks to the app in-process; no server/socket required.
|
|
31
|
+
transport = httpx2.ASGITransport(app=main.app)
|
|
32
|
+
async with httpx2.AsyncClient(transport=transport, base_url="http://testserver") as client:
|
|
33
|
+
return await client.get(path)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_sync(path: str) -> httpx2.Response:
|
|
37
|
+
# Run the async request from a plain sync test (no pytest-asyncio needed).
|
|
38
|
+
return asyncio.run(_get(path))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _guest_redirect_for(path: str) -> str | None:
|
|
42
|
+
"""Sign-in URL a signed-out visitor is sent to, or None if `path` is allowed through.
|
|
43
|
+
|
|
44
|
+
Mirrors the guest branches of `AuthMiddleware` in the order it applies them.
|
|
45
|
+
Importing `main` has already run `configure_auth(build_auth_settings())`, so
|
|
46
|
+
the shared `Auth` instance carries this app's real policy.
|
|
47
|
+
"""
|
|
48
|
+
auth = Auth.get_instance()
|
|
49
|
+
if auth.is_public_route(path) or auth.is_auth_route(path):
|
|
50
|
+
return None
|
|
51
|
+
if auth.settings.is_role_based and auth.get_required_roles(path):
|
|
52
|
+
return auth.get_signin_redirect(path)
|
|
53
|
+
if auth.is_private_route(path):
|
|
54
|
+
return auth.get_signin_redirect(path)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_health_returns_ok():
|
|
59
|
+
redirect = _guest_redirect_for("/health")
|
|
60
|
+
if redirect is not None:
|
|
61
|
+
pytest.skip(f"/health is auth-gated by this app's auth policy (redirects to {redirect})")
|
|
62
|
+
response = _get_sync("/health")
|
|
63
|
+
assert response.status_code == 200
|
|
64
|
+
assert response.json() == {"status": "ok"}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_health_carries_security_headers():
|
|
68
|
+
# Runs whatever the policy decides: the headers are attached to the redirect
|
|
69
|
+
# too, because SecurityHeadersMiddleware sits outside AuthMiddleware.
|
|
70
|
+
response = _get_sync("/health")
|
|
71
|
+
assert response.headers.get("x-content-type-options") == "nosniff"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_unknown_route_matches_auth_policy():
|
|
75
|
+
"""An unknown path 404s when it is reachable, and redirects when it is not."""
|
|
76
|
+
response = _get_sync(UNKNOWN_PATH)
|
|
77
|
+
redirect = _guest_redirect_for(UNKNOWN_PATH)
|
|
78
|
+
|
|
79
|
+
if redirect is None:
|
|
80
|
+
# Public-first: nothing guards the path, so routing answers with the 404 page.
|
|
81
|
+
assert response.status_code == 404
|
|
82
|
+
else:
|
|
83
|
+
# All-private: the guest never reaches the router.
|
|
84
|
+
assert response.status_code == 303
|
|
85
|
+
assert response.headers.get("location") == redirect
|