🧪 p3: tests for async callback path, native_async marker, sse_native
swadhinbiswas committed 2 months ago
1ad2e27
1 parent 364904a
commit 1ad2e27
3 files changed
+72 -0
Showing 3 changed files+72-0
▼crates/justapi-py/python/justapi/test_background.py
+33-0
| @@ -130,3 +130,36 @@ async def test_async_handler_interleaves_concurrent_requests(): | ||
130 | 130 | # 20 requests × 50ms serialized would take >= 1.0s. Interleaved, they |
131 | 131 | # should finish well under that (allow generous CI margin). |
132 | 132 | assert elapsed < 0.8, f"async handlers serialized: 20×50ms took {elapsed:.2f}s" |
133 | + | |
134 | + | |
135 | +@pytest.mark.asyncio | |
136 | +async def test_async_callback_path_concurrent_correctness(): | |
137 | + """Stress the callback-driven async resolution: many concurrent async | |
138 | + handlers (mix of success and exception) must all complete correctly. | |
139 | + Guards the `_DoneNotifier` add_done_callback path (ADR-086).""" | |
140 | + import asyncio | |
141 | + from justapi import JustAPIApp, HTTPException | |
142 | + from justapi.testing import AsyncTestClient | |
143 | + | |
144 | + app = JustAPIApp() | |
145 | + | |
146 | + @app.get("/ok") | |
147 | + async def ok(req): | |
148 | + await asyncio.sleep(0.01) | |
149 | + return {"ok": True} | |
150 | + | |
151 | + @app.get("/err") | |
152 | + async def err(req): | |
153 | + await asyncio.sleep(0.01) | |
154 | + raise HTTPException(status_code=404, detail="gone") | |
155 | + | |
156 | + async with AsyncTestClient(app) as client: | |
157 | + results = await asyncio.gather( | |
158 | + *(client.get("/ok") for _ in range(30)), | |
159 | + *(client.get("/err") for _ in range(30)), | |
160 | + return_exceptions=True, | |
161 | + ) | |
162 | + oks = [r for r in results if isinstance(r, dict) and r.get("status") == 200] | |
163 | + errs = [r for r in results if isinstance(r, dict) and r.get("status") == 404] | |
164 | + assert len(oks) == 30, f"expected 30 success, got {len(oks)}" | |
165 | + assert len(errs) == 30, f"expected 30 errors, got {len(errs)}" | |
133 | 166 | |
▼crates/justapi-py/python/justapi/test_native.py
+22-0
| @@ -171,3 +171,25 @@ def test_justapp_alias_is_justapiapp(): | ||
171 | 171 | |
172 | 172 | resp = JustAPITestClient(app).get("/") |
173 | 173 | assert resp["status"] == 200 |
174 | + | |
175 | + | |
176 | +def test_native_async_marker(): | |
177 | + """@native_async marks a handler; it works like any async handler but is | |
178 | + flagged for the fastest dispatch (parallel on free-threaded builds, ADR-089).""" | |
179 | + import asyncio | |
180 | + from justapi import JustAPIApp, native_async, JustAPITestClient | |
181 | + | |
182 | + app = JustAPIApp() | |
183 | + | |
184 | + @app.get("/x") | |
185 | + @native_async | |
186 | + async def handler(request): | |
187 | + await asyncio.sleep(0.001) | |
188 | + return {"ok": True, "path": request.get("path")} | |
189 | + | |
190 | + r = JustAPITestClient(app).get("/x") | |
191 | + assert r["status"] == 200 | |
192 | + assert b"ok" in bytes(r["body"]) | |
193 | + # The decorator marks the raw function; the wrapper propagation is | |
194 | + # verified by the Rust dispatch using the marker (integration-tested). | |
195 | + assert getattr(handler, "__native_async__", False) is True | |
174 | 196 | |
▼crates/justapi-py/python/justapi/test_sse.py
+17-0
| @@ -23,3 +23,20 @@ async def test_sse_streaming(): | ||
23 | 23 | print("Response body:", repr(body)) |
24 | 24 | for i in range(5): |
25 | 25 | assert f"data: {i}\n\n" in body |
26 | + | |
27 | + | |
28 | +def test_sse_native_rust_stream(): | |
29 | + """Rust-native SSE stream (ADR-088): events generated entirely in Rust, | |
30 | + no Python handler — must work on the real server AND test client.""" | |
31 | + from justapi import JustAPIApp, JustAPITestClient | |
32 | + | |
33 | + app = JustAPIApp() | |
34 | + app.sse_native("/events", count=3, interval_ms=0) | |
35 | + | |
36 | + r = JustAPITestClient(app).get("/events") | |
37 | + assert r["status"] == 200 | |
38 | + body = bytes(r["body"]).decode() | |
39 | + assert 'data: {"n":1}' in body | |
40 | + assert 'data: {"n":3}' in body | |
41 | + # content-type is event-stream | |
42 | + assert r["headers"].get("content-type", "").startswith("text/event-stream") | |
26 | 43 | |