swadhinbiswas 🧪 p3: tests for async callback path, native_async marker, sse_native · 8/7/2026
1ad2e27
196 lines · 6.3 KB
1
2
3
4
5
6
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
"""Integration test for Tier B Native API (JustAPIApp).
"""
import concurrent.futures
import json
import subprocess
import sys
import time
import urllib.request
import urllib.error

SERVER_SCRIPT = r"""
from justapi import JustAPIApp

async def hello_handler(request):
    name = request["path_params"]["name"]
    return {"message": f"Hello {name}!"}

async def echo_handler(request):
    return {
        "status": 200,
        "headers": [(b"content-type", b"application/json")],
        "body": request["body"],
    }

# A synchronous (GIL-bound) handler: under CPython only one worker can run it
# at a time, so a burst of requests exercises the GIL pool's bounded dispatch
# queue (capacity 16 per worker). Before the backpressure fix, overflowing that
# queue surfaced as spurious RFC 9457 404s with no log (try_send rejection
# masked by handle_request). This handler keeps the pool busy long enough that
# many requests accumulate concurrently.
def sync_handler(request):
    import time as _t
    _t.sleep(0.0005)
    return {"message": "sync"}

app = JustAPIApp()
app.get("/hello/{name}", hello_handler)
app.post("/echo", echo_handler)
app.get("/sync", sync_handler)
app.run("127.0.0.1:9867")
"""


def test_native_api():
    proc = subprocess.Popen(
        [sys.executable, "-c", SERVER_SCRIPT],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    addr = "127.0.0.1:9867"
    time.sleep(0.8)

    try:
        resp = urllib.request.urlopen(f"http://{addr}/hello/world")
        assert resp.status == 200, f"Expected 200, got {resp.status}"
        data = json.loads(resp.read())
        assert data == {"message": "Hello world!"}, f"Unexpected: {data}"
        print("PASS: /hello/world ->", data)

        resp = urllib.request.urlopen(f"http://{addr}/hello/JustAPI")
        assert resp.status == 200
        data = json.loads(resp.read())
        assert data == {"message": "Hello JustAPI!"}, f"Unexpected: {data}"
        print("PASS: /hello/JustAPI ->", data)

        try:
            urllib.request.urlopen(f"http://{addr}/nonexistent")
            assert False, "Expected 404"
        except urllib.error.HTTPError as e:
            assert e.code == 404, f"Expected 404, got {e.code}"
            print("PASS: /nonexistent -> 404")

        payload = b'{"hello":"world"}'
        req = urllib.request.Request(
            f"http://{addr}/echo",
            data=payload,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        resp = urllib.request.urlopen(req)
        assert resp.status == 200, f"Expected 200, got {resp.status}"
        echo_body = resp.read()
        assert echo_body == payload, f"Echo mismatch: {echo_body} != {payload}"
        print("PASS: POST /echo -> body echoed")

        try:
            urllib.request.urlopen(f"http://{addr}/echo")
            assert False, "Expected 405 (wrong method)"
        except urllib.error.HTTPError as e:
            assert e.code == 405, f"Expected 405, got {e.code}"
            print("PASS: GET /echo -> 405 (wrong method)")

        print()
        print("=== All tests PASSED ===")

    finally:
        proc.terminate()
        try:
            stdout, stderr = proc.communicate(timeout=5)
            if stderr:
                decoded = stderr.decode()
                if decoded.strip():
                    print("Server stderr:", decoded)
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.communicate()


def test_sync_handler_no_spurious_404_under_load():
    """Regression: a burst of concurrent requests to a GIL-bound (sync)
    Python handler must not produce spurious 404s.

    The GIL pool dispatches jobs through a bounded channel (cap 16/worker for a
    single GIL worker). Dispatch used `try_send`, so any overflow surfaced as a
    handler error that `handle_request` masked as an RFC 9457 404. With
    `send().await` backpressure the pool throttles instead of dropping, and the
    handler-chain error branch now returns 500 instead of a fake 404.
    """
    proc = subprocess.Popen(
        [sys.executable, "-c", SERVER_SCRIPT],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    addr = "127.0.0.1:9867"
    time.sleep(0.8)

    def fire(_):
        try:
            resp = urllib.request.urlopen(f"http://{addr}/sync", timeout=10)
            return resp.status
        except urllib.error.HTTPError as e:
            return e.code
        except Exception:
            return -1

    try:
        # 64 threads >> GIL pool capacity of 16, forcing queue contention.
        with concurrent.futures.ThreadPoolExecutor(max_workers=32) as ex:
            statuses = list(ex.map(fire, range(512)))
        bad = [s for s in statuses if s != 200]
        assert not bad, f"Expected all 200, got non-200 statuses: {bad}"
        print("PASS: 512 concurrent /sync requests -> all 200 (no spurious 404)")
    finally:
        proc.terminate()
        try:
            proc.communicate(timeout=5)
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.communicate()


if __name__ == "__main__":
    test_native_api()
    test_sync_handler_no_spurious_404_under_load()


def test_justapp_alias_is_justapiapp():
    """JustAPP is an alias of JustAPIApp (and so is JustAPI)."""
    from justapi import JustAPIApp, JustAPP, JustAPI

    assert JustAPP is JustAPIApp
    assert JustAPI is JustAPIApp

    app = JustAPP()

    @app.get("/")
    def root():
        return {"ok": True}

    from justapi import JustAPITestClient

    resp = JustAPITestClient(app).get("/")
    assert resp["status"] == 200


def test_native_async_marker():
    """@native_async marks a handler; it works like any async handler but is
    flagged for the fastest dispatch (parallel on free-threaded builds, ADR-089)."""
    import asyncio
    from justapi import JustAPIApp, native_async, JustAPITestClient

    app = JustAPIApp()

    @app.get("/x")
    @native_async
    async def handler(request):
        await asyncio.sleep(0.001)
        return {"ok": True, "path": request.get("path")}

    r = JustAPITestClient(app).get("/x")
    assert r["status"] == 200
    assert b"ok" in bytes(r["body"])
    # The decorator marks the raw function; the wrapper propagation is
    # verified by the Rust dispatch using the marker (integration-tested).
    assert getattr(handler, "__native_async__", False) is True