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.
@@ -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
- import asyncio
10
-
11
- import httpx2
12
-
13
- import main
14
-
15
-
16
- async def _get(path: str) -> httpx2.Response:
17
- # ASGITransport speaks to the app in-process; no server/socket required.
18
- transport = httpx2.ASGITransport(app=main.app)
19
- async with httpx2.AsyncClient(transport=transport, base_url="http://testserver") as client:
20
- return await client.get(path)
21
-
22
-
23
- def _get_sync(path: str) -> httpx2.Response:
24
- # Run the async request from a plain sync test (no pytest-asyncio needed).
25
- return asyncio.run(_get(path))
26
-
27
-
28
- def test_health_returns_ok():
29
- response = _get_sync("/health")
30
- assert response.status_code == 200
31
- assert response.json() == {"status": "ok"}
32
-
33
-
34
- def test_health_carries_security_headers():
35
- response = _get_sync("/health")
36
- # SecurityHeadersMiddleware attaches baseline browser headers.
37
- assert response.headers.get("x-content-type-options") == "nosniff"
38
-
39
-
40
- def test_unknown_route_is_404():
41
- response = _get_sync("/definitely-not-a-real-route")
42
- assert response.status_code == 404
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "1.6.3-alpha",
3
+ "version": "1.6.4-alpha",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",