simple-module-test 0.0.19__tar.gz → 0.0.20__tar.gz

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.
@@ -61,3 +61,4 @@ Thumbs.db
61
61
  .playwright-mcp/*
62
62
  host/client_app/.playwright-cli/*
63
63
  .superpowers/
64
+ .qa/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple_module_test
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: Shared pytest fixtures (app, client, db_session, authenticated_client) for writing simple_module module tests
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -25,9 +25,9 @@ Requires-Dist: fastapi>=0.115
25
25
  Requires-Dist: httpx>=0.27
26
26
  Requires-Dist: pytest-asyncio>=0.24
27
27
  Requires-Dist: pytest>=8.0
28
- Requires-Dist: simple-module-core==0.0.19
29
- Requires-Dist: simple-module-db==0.0.19
30
- Requires-Dist: simple-module-hosting==0.0.19
28
+ Requires-Dist: simple-module-core==0.0.20
29
+ Requires-Dist: simple-module-db==0.0.20
30
+ Requires-Dist: simple-module-hosting==0.0.20
31
31
  Requires-Dist: sqlalchemy>=2.0
32
32
  Description-Content-Type: text/markdown
33
33
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_test"
3
- version = "0.0.19"
3
+ version = "0.0.20"
4
4
  description = "Shared pytest fixtures (app, client, db_session, authenticated_client) for writing simple_module module tests"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -25,9 +25,9 @@ dependencies = [
25
25
  "httpx>=0.27",
26
26
  "pytest>=8.0",
27
27
  "pytest-asyncio>=0.24",
28
- "simple_module_core==0.0.19",
29
- "simple_module_db==0.0.19",
30
- "simple_module_hosting==0.0.19",
28
+ "simple_module_core==0.0.20",
29
+ "simple_module_db==0.0.20",
30
+ "simple_module_hosting==0.0.20",
31
31
  "sqlalchemy>=2.0",
32
32
  ]
33
33
 
@@ -22,11 +22,13 @@ module to be installed — it seeds an admin via ``users.bootstrap``.
22
22
 
23
23
  from simple_module_test.app_factory import build_test_app
24
24
  from simple_module_test.fake_events import FakeEventBus, RecordedEvent
25
+ from simple_module_test.routes import effective_route_paths
25
26
  from simple_module_test.session_cookie import forge_session_cookie
26
27
 
27
28
  __all__ = [
28
29
  "FakeEventBus",
29
30
  "RecordedEvent",
30
31
  "build_test_app",
32
+ "effective_route_paths",
31
33
  "forge_session_cookie",
32
34
  ]
@@ -0,0 +1,34 @@
1
+ """Enumerate an app's effective route paths, robust to lazy router inclusion.
2
+
3
+ FastAPI 0.137 / Starlette 1.3 made ``include_router()`` lazy: an included router
4
+ now appears in ``app.routes`` as a ``_IncludedRouter`` wrapper that carries no
5
+ ``.path`` and resolves its routes only at request time. Code that introspects
6
+ ``app.routes`` by ``.path`` therefore no longer sees routes contributed via
7
+ ``include_router`` — including every module's API/view routes and the health
8
+ router. (Routes still resolve correctly at request time; only static
9
+ introspection broke.)
10
+
11
+ :func:`effective_route_paths` reads the OpenAPI schema instead — a stable public
12
+ API that lists every schema-included route with its fully-resolved prefix — and
13
+ unions it with any top-level routes/mounts that still carry a ``.path`` directly
14
+ (e.g. ``StaticFiles`` mounts). This works across FastAPI versions and is the
15
+ supported way to assert on registered routes.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from fastapi import FastAPI
21
+
22
+
23
+ def effective_route_paths(app: FastAPI) -> set[str]:
24
+ """Return the set of route paths registered on ``app``.
25
+
26
+ Includes schema routes contributed via ``include_router`` (resolved through
27
+ the OpenAPI schema, so FastAPI's lazy ``_IncludedRouter`` wrappers don't hide
28
+ them) plus any top-level mounts. Routes registered with
29
+ ``include_in_schema=False`` — e.g. the bare-prefix Inertia aliases — are not
30
+ listed here; assert those with a request instead.
31
+ """
32
+ paths = set(app.openapi().get("paths", {}).keys())
33
+ paths |= {r.path for r in app.routes if getattr(r, "path", None) is not None}
34
+ return paths
@@ -46,11 +46,10 @@ class TestBuildTestApp:
46
46
 
47
47
  async def test_registers_module_routes_with_prefix(self):
48
48
  """The module's register_routes() runs and its api routes appear under the prefix."""
49
- from simple_module_test import build_test_app
49
+ from simple_module_test import build_test_app, effective_route_paths
50
50
 
51
51
  app = build_test_app(_EchoModule)
52
- paths = {getattr(r, "path", None) for r in app.routes}
53
- assert "/api/echo/ping" in paths
52
+ assert "/api/echo/ping" in effective_route_paths(app)
54
53
 
55
54
  async def test_module_accessible_on_app_state(self):
56
55
  """The instance is stored on app.state.module so tests can poke at it."""
@@ -61,12 +60,12 @@ class TestBuildTestApp:
61
60
 
62
61
  async def test_isolates_from_other_installed_modules(self):
63
62
  """build_test_app only mounts the given module — Products/Auth routes are absent."""
64
- from simple_module_test import build_test_app
63
+ from simple_module_test import build_test_app, effective_route_paths
65
64
 
66
65
  app = build_test_app(_EchoModule)
67
- paths = {getattr(r, "path", None) for r in app.routes}
68
- assert not any(p and p.startswith("/api/products") for p in paths)
69
- assert not any(p and p.startswith("/auth") for p in paths)
66
+ paths = effective_route_paths(app)
67
+ assert not any(p.startswith("/api/products") for p in paths)
68
+ assert not any(p.startswith("/auth") for p in paths)
70
69
 
71
70
 
72
71
  # ── pytest plugin fixtures ──────────────────────────────────────────