@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.1

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.
Files changed (96) hide show
  1. package/AGENTS.md +147 -147
  2. package/BENCHMARKS.md +262 -262
  3. package/GATEWAY.md +272 -272
  4. package/NOTICE +28 -28
  5. package/PRIVACY.md +46 -46
  6. package/README.md +368 -314
  7. package/SECURITY.md +83 -83
  8. package/TERMS.md +22 -22
  9. package/USAGE.md +170 -163
  10. package/VERSION +1 -1
  11. package/bin/benchwarmers.js +16 -74
  12. package/cli.py +666 -0
  13. package/examples/config/gateway.json +5 -5
  14. package/examples/config/model-roles.json +11 -11
  15. package/gateway.py +755 -755
  16. package/launcher.py +120 -0
  17. package/mcp_server.py +266 -266
  18. package/metrics/alerts.py +116 -116
  19. package/metrics/aliases.py +41 -41
  20. package/metrics/bench_collections.py +141 -141
  21. package/metrics/catalog.py +295 -295
  22. package/metrics/client.py +394 -394
  23. package/metrics/cliproxy.py +191 -191
  24. package/metrics/connect.py +691 -691
  25. package/metrics/cost.py +152 -152
  26. package/metrics/decide.py +227 -227
  27. package/metrics/discover.py +318 -318
  28. package/metrics/endpoints.py +436 -436
  29. package/metrics/guard.py +617 -592
  30. package/metrics/learn.py +221 -221
  31. package/metrics/litellm_hub.py +206 -206
  32. package/metrics/model_roles.py +84 -84
  33. package/metrics/offload.py +166 -166
  34. package/metrics/owui.py +205 -205
  35. package/metrics/paths.py +60 -60
  36. package/metrics/route.py +579 -579
  37. package/metrics/scheduler.py +275 -275
  38. package/metrics/sources.py +167 -167
  39. package/metrics/wrong.py +144 -144
  40. package/package.json +68 -65
  41. package/perf/README.md +89 -89
  42. package/perf/run.py +261 -261
  43. package/scripts/auto_retire.py +367 -367
  44. package/scripts/check.py +108 -106
  45. package/scripts/check_deprecations.py +378 -378
  46. package/scripts/check_themes.py +103 -103
  47. package/scripts/init.py +124 -124
  48. package/scripts/list_served.py +78 -78
  49. package/scripts/notice.py +78 -78
  50. package/scripts/onboard.py +401 -331
  51. package/scripts/provider_tags.py +129 -129
  52. package/scripts/reprefix.py +136 -136
  53. package/scripts/sync_config.py +105 -105
  54. package/scripts/test_auth.py +255 -255
  55. package/scripts/test_cli.py +189 -0
  56. package/scripts/test_client.py +233 -233
  57. package/scripts/test_connect.py +222 -222
  58. package/scripts/test_first_run.py +180 -180
  59. package/scripts/test_hubs.py +328 -328
  60. package/scripts/test_launcher.py +67 -0
  61. package/scripts/test_llm_log.py +85 -81
  62. package/scripts/test_mcp.py +178 -178
  63. package/scripts/test_merged.py +214 -214
  64. package/scripts/test_no_owui.py +291 -291
  65. package/scripts/test_owui.py +90 -90
  66. package/scripts/test_serve_edit.py +448 -438
  67. package/scripts/test_verdict.py +98 -98
  68. package/scripts/test_vision.py +228 -228
  69. package/serve.py +3904 -3764
  70. package/suite/mine.py +154 -154
  71. package/suite/pace.py +145 -145
  72. package/suite/run.py +1046 -1042
  73. package/suite/seed.py +421 -421
  74. package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
  75. package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
  76. package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
  77. package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
  78. package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
  79. package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
  80. package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
  81. package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
  82. package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
  83. package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
  84. package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
  85. package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
  86. package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
  87. package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
  88. package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
  89. package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
  90. package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
  91. package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
  92. package/ui/bench.css +430 -425
  93. package/ui/bench.js +1756 -1576
  94. package/ui/themes/dark.css +35 -35
  95. package/ui/themes/hardwood.css +52 -52
  96. package/ui/themes/rainforest.css +32 -32
@@ -1,255 +1,255 @@
1
- """Login, gateway keys and one port, through the real handlers. No real files.
2
-
3
- python scripts/test_auth.py
4
-
5
- metrics/guard.py's self-check covers the pieces (the cookie, key hashing,
6
- the startup refusal); this covers what the servers do with them, route by
7
- route: the dashboard behind the login cookie, the gateway's routes behind a
8
- gateway key on both ports, the open routes (health, login, Connect's
9
- callback), no auth at all without an admin token, the access log, and both
10
- servers refusing to listen beyond this machine without a token. Keys, the
11
- change log and collections point at a temp folder.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import io
17
- import json
18
- import os
19
- import pathlib
20
- import subprocess
21
- import sys
22
- import tempfile
23
- import threading
24
- import urllib.error
25
- import urllib.request
26
- from http.server import ThreadingHTTPServer
27
-
28
- ROOT = pathlib.Path(__file__).resolve().parent.parent
29
- sys.path.insert(0, str(ROOT))
30
- import serve # noqa: E402
31
- import gateway # noqa: E402
32
- import guard # noqa: E402
33
-
34
- TOKEN = "test-admin-token-0123456789"
35
-
36
-
37
- class NoRedirect(urllib.request.HTTPRedirectHandler):
38
- def redirect_request(self, *args, **kwargs):
39
- return None
40
-
41
-
42
- OPENER = urllib.request.build_opener(NoRedirect)
43
-
44
-
45
- def serve_on(handler) -> ThreadingHTTPServer:
46
- server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
47
- threading.Thread(target=server.serve_forever, daemon=True).start()
48
- return server
49
-
50
-
51
- def call(port: int, path: str, body: dict | None = None, method: str | None = None,
52
- **headers) -> tuple[int, dict, str]:
53
- """(status, headers, body text) for one request; HTTP errors are answers too."""
54
- headers = {"Host": f"127.0.0.1:{port}", **headers}
55
- data = None
56
- if body is not None:
57
- data, headers["Content-Type"] = json.dumps(body).encode(), "application/json"
58
- req = urllib.request.Request(f"http://127.0.0.1:{port}{path}", data, headers, method=method)
59
- try:
60
- with OPENER.open(req, timeout=10) as r:
61
- return r.status, dict(r.headers), r.read().decode()
62
- except urllib.error.HTTPError as e:
63
- return e.code, dict(e.headers), e.read().decode()
64
-
65
-
66
- def refuses_to_start(script: str) -> str:
67
- """The startup message when `script` is told to listen beyond this machine without a token."""
68
- env = {k: v for k, v in os.environ.items() if k != "BENCHWARMERS_ADMIN_TOKEN"}
69
- env.update(BENCH_BIND="0.0.0.0", BENCH_PORT="8199", BENCH_GATEWAY_PORT="8198", PYTHONIOENCODING="utf-8")
70
- done = subprocess.run([sys.executable, script], cwd=ROOT, env=env, capture_output=True, text=True,
71
- encoding="utf-8", errors="replace", timeout=120)
72
- assert done.returncode != 0, f"{script} started on 0.0.0.0 without a token"
73
- return done.stderr
74
-
75
-
76
- def main() -> int:
77
- for script in ("serve.py", "gateway.py"):
78
- assert "BENCHWARMERS_ADMIN_TOKEN" in refuses_to_start(script), script
79
-
80
- dash, gw = serve_on(serve.Handler), serve_on(gateway.Handler)
81
- d, g = dash.server_port, gw.server_port
82
- saved = (guard.KEYS, serve.CHANGES_PATH, serve.PORT, gateway.bench_collections.FILE,
83
- os.environ.pop("BENCHWARMERS_ADMIN_TOKEN", None), sys.stderr, gateway.run_collection,
84
- os.environ.pop("BENCHWARMERS_REQUIRE_KEYS", None), os.environ.pop("BENCH_ALLOWED_ORIGINS", None))
85
- with tempfile.TemporaryDirectory() as tmp:
86
- t = pathlib.Path(tmp)
87
- try:
88
- guard.KEYS, serve.CHANGES_PATH, serve.PORT = t / "gateway-keys.json", t / "changes.jsonl", d
89
- gateway.bench_collections.FILE = t / "collections.json"
90
- gateway.bench_collections.FILE.write_text(json.dumps({"core": {"members": [], "description": "x"},
91
- "vision": {"members": [], "description": "x"}}),
92
- encoding="utf-8")
93
- # The panel is test_merged's and test_vision's; here only who asked, and whether it ran.
94
- ran = []
95
- gateway.run_collection = lambda name, messages, nominated, reply, who: (
96
- ran.append((name, who)) or "ok", {"seats": []})
97
-
98
- # NO TOKEN: everything as before -- no login, no key, any bearer ignored.
99
- assert call(d, "/api/stamp")[0] == 200
100
- assert call(d, "/login")[:2][0] == 303
101
- for port in (d, g):
102
- status, _, text = call(port, "/v1/models", Authorization="Bearer sk-anything")
103
- assert status == 200 and "bench/core" in text, (port, status, text)
104
-
105
- # TOKEN SET: the dashboard asks for the login cookie...
106
- os.environ["BENCHWARMERS_ADMIN_TOKEN"] = TOKEN
107
- status, headers, _ = call(d, "/")
108
- assert status == 303 and headers["Location"] == "/login", (status, headers)
109
- assert call(d, "/api/stamp")[0] == 401
110
- assert call(d, "/api/theme", {"theme": "nope"})[0] == 401 # writes too
111
- # Not even which routes exist: the Host, then the login, then the 404.
112
- assert call(d, "/api/nope", {}, Host="evil.example")[0] == 403
113
- assert call(d, "/api/nope", {})[0] == 401
114
- assert call(d, "/fonts/chivo-latin-400-normal.woff2")[0] == 303
115
- status, _, page = call(d, "/login")
116
- assert status == 200 and "Admin token" in page
117
- # ...which only the admin token gets, through the same guard as any write.
118
- assert call(d, "/login", {"token": "wrong"})[0] == 401
119
- assert call(d, "/login", {"token": TOKEN}, Origin="http://evil.example")[0] == 403
120
- status, headers, _ = call(d, "/login", {"token": TOKEN})
121
- cookie = headers.get("Set-Cookie", "")
122
- assert status == 200 and "HttpOnly" in cookie and "SameSite=Strict" in cookie, headers
123
- assert TOKEN not in cookie
124
- jar = {"Cookie": cookie.split(";", 1)[0]}
125
- assert call(d, "/api/stamp", **jar)[0] == 200
126
- assert call(d, "/api/theme", {"theme": "nope"}, **jar)[0] == 400 # in, then the theme rule
127
- assert call(d, "/api/nope", {}, **jar)[0] == 404
128
-
129
- # Open without either: health (any Host), and Connect's callback.
130
- assert call(d, "/health", Host="platform.internal:9")[:3:2] == (200, '{"ok": true}')
131
- status, _, page = call(d, "/connect/callback?state=nope&code=x")
132
- assert status == 200 and "unknown or has expired" in page, page
133
- # ...but not a path merely beginning like it.
134
- assert call(d, "/connect/callbackx")[0] == 303
135
-
136
- # Gateway keys, made from the dashboard and shown once.
137
- assert call(d, "/api/gateway-key", {"name": "chat-ui"})[0] == 401
138
- status, _, text = call(d, "/api/gateway-key", {"name": "chat-ui"}, **jar)
139
- key = json.loads(text)["key"]
140
- assert status == 200 and key not in guard.KEYS.read_text(encoding="utf-8")
141
- assert key not in serve.CHANGES_PATH.read_text(encoding="utf-8")
142
- assert key not in serve.settings_page() and "chat-ui" in serve.settings_page()
143
- bearer = {"Authorization": f"Bearer {key}"}
144
-
145
- # The gateway's routes, on both ports: the key, never the cookie.
146
- for port in (d, g):
147
- status, headers, _ = call(port, "/v1/models")
148
- assert status == 401 and headers["WWW-Authenticate"].startswith("Bearer"), (port, headers)
149
- assert call(port, "/v1/models", **jar)[0] == 401, port
150
- assert call(port, "/v1/models", Authorization="Bearer sk-dummy")[0] == 401, port
151
- status, _, text = call(port, "/v1/models", **bearer)
152
- assert status == 200 and json.loads(text)["data"][0]["id"] == "bench/core", (port, text)
153
- # One port: /bench/verdict reaches the gateway's own validation.
154
- status, _, text = call(port, "/bench/verdict", {"call": "x"}, **bearer)
155
- assert status == 400 and "useful" in json.loads(text)["error"]["message"], (port, text)
156
- assert call(port, "/v1/models", Host="evil.example", **bearer)[0] == 403, port # Host still checked
157
- # A key never opens the dashboard.
158
- assert call(d, "/api/stamp", **bearer)[0] == 401
159
- assert call(d, "/", **bearer)[0] == 303
160
-
161
- # The access log: route, status, who; never a key, a query or a callback's code.
162
- log = io.StringIO()
163
- sys.stderr = log
164
- call(d, "/v1/models", **bearer)
165
- call(d, "/api/stamp", **jar)
166
- call(d, "/connect/callback?state=nope&code=secret-code")
167
- call(d, "/connect/callback/sid-in-path?code=secret-code")
168
- sys.stderr = saved[5]
169
- lines = log.getvalue()
170
- assert "GET /v1/models 200 chat-ui" in lines and "GET /api/stamp 200 admin" in lines, lines
171
- assert key not in lines and "secret-code" not in lines and "sid-in-path" not in lines, lines
172
-
173
- # A key limited to collections and a rate, made on Settings: it sees and calls only
174
- # those, its name rides on the call, and past its cap it waits.
175
- status, _, text = call(d, "/api/gateway-key", {"name": "estate", "collections": ["vision"], "rpm": "1"},
176
- **jar)
177
- assert status == 200, text
178
- estate = {"Authorization": f"Bearer {json.loads(text)['key']}"}
179
- assert call(d, "/api/gateway-key", {"name": "other", "collections": ["nope"]}, **jar)[0] == 400
180
- assert call(d, "/api/gateway-key", {"name": "other", "rpm": "lots"}, **jar)[0] == 400
181
- status, _, text = call(g, "/v1/models", **estate)
182
- assert [m["id"] for m in json.loads(text)["data"]] == ["bench/vision", "bench/vision:merged"], text
183
- ask = {"messages": [{"role": "user", "content": "hi"}]}
184
- status, _, text = call(g, "/v1/chat/completions", {"model": "bench/core", **ask}, **estate)
185
- assert status == 403 and "may call bench/vision" in text, text
186
- assert call(g, "/v1/chat/completions", {"model": "bench/vision", **ask}, **estate)[0] == 200
187
- assert call(g, "/v1/chat/completions", {"model": "bench/core", **ask}, **bearer)[0] == 200
188
- assert ran == [("vision", "estate"), ("core", "chat-ui")], ran
189
- status, headers, _ = call(g, "/v1/chat/completions", {"model": "bench/vision", **ask}, **estate)
190
- assert status == 429 and int(headers["Retry-After"]) >= 1, (status, headers)
191
- page = serve.settings_page()
192
- assert "estate" in page and "bwk_…" in page and ">vision<" in page, page
193
-
194
- # CORS for a browser app on an allowed origin, on the gateway's routes only.
195
- os.environ["BENCH_ALLOWED_ORIGINS"] = "https://estate.example"
196
- app, evil = {"Origin": "https://estate.example"}, {"Origin": "https://evil.example"}
197
- asks = {"Access-Control-Request-Method": "POST",
198
- "Access-Control-Request-Headers": "authorization, content-type, x-stainless-os"}
199
- for port in (d, g):
200
- status, headers, _ = call(port, "/v1/chat/completions", method="OPTIONS", **app, **asks)
201
- assert status == 204 and headers["Access-Control-Allow-Origin"] == "https://estate.example", headers
202
- assert "Authorization" in headers["Access-Control-Allow-Headers"] and headers["Vary"] == "Origin"
203
- assert "x-stainless-os" in headers["Access-Control-Allow-Headers"] # the OpenAI JS SDK's
204
- assert "Access-Control-Allow-Credentials" not in headers
205
- status, headers, _ = call(port, "/v1/models", method="OPTIONS", **app,
206
- **{"Access-Control-Request-Headers": "x-ok; not a name"})
207
- assert status == 204 and headers["Access-Control-Allow-Headers"] == "Authorization, Content-Type"
208
- status, headers, _ = call(port, "/v1/chat/completions", method="OPTIONS", **evil, **asks)
209
- assert status == 403 and "Access-Control-Allow-Origin" not in headers, (port, headers)
210
- status, headers, _ = call(port, "/v1/models", **bearer, **app)
211
- assert status == 200 and headers["Access-Control-Allow-Origin"] == "https://estate.example"
212
- assert "Access-Control-Allow-Origin" not in call(port, "/v1/models", **bearer, **evil)[1]
213
- # The app reads a refusal too; cross-site is fine for it, for nobody else.
214
- assert call(port, "/v1/models", **app)[1]["Access-Control-Allow-Origin"] == "https://estate.example"
215
- status, _, text = call(port, "/bench/verdict", {"call": "x"}, **bearer, **app,
216
- **{"Sec-Fetch-Site": "cross-site"})
217
- assert status == 400 and "useful" in text, (port, text)
218
- assert call(port, "/bench/verdict", {"call": "x"}, **bearer, **evil,
219
- **{"Sec-Fetch-Site": "cross-site"})[0] == 403
220
- # The dashboard: no preflight, no CORS, same-origin as before.
221
- assert call(d, "/api/stamp", method="OPTIONS", **app, **asks)[0] == 501
222
- status, headers, _ = call(d, "/api/theme", {"theme": "nope"}, **jar, **app, **{"Sec-Fetch-Site": "cross-site"})
223
- assert status == 403 and "Access-Control-Allow-Origin" not in headers, headers
224
- assert "Access-Control-Allow-Origin" not in call(d, "/api/stamp", **jar, **app)[1]
225
- del os.environ["BENCH_ALLOWED_ORIGINS"]
226
-
227
- # Revoked: refused at once.
228
- assert call(d, "/api/gateway-key/remove", {"name": "chat-ui"}, **jar)[0] == 200
229
- assert call(d, "/v1/models", **bearer)[0] == 401
230
- assert call(d, "/api/gateway-key/remove", {"name": "chat-ui"}, **jar)[0] == 400
231
-
232
- # No admin token, keys required on this machine: the gateway asks, the dashboard does not.
233
- del os.environ["BENCHWARMERS_ADMIN_TOKEN"]
234
- os.environ["BENCHWARMERS_REQUIRE_KEYS"] = "1"
235
- for port in (d, g):
236
- assert call(port, "/v1/models", Authorization="Bearer sk-anything")[0] == 401, port
237
- assert call(port, "/v1/models", **estate)[0] == 200, port
238
- assert call(d, "/api/stamp")[0] == 200
239
- finally:
240
- (guard.KEYS, serve.CHANGES_PATH, serve.PORT, gateway.bench_collections.FILE) = saved[:4]
241
- gateway.run_collection = saved[6]
242
- for name, value in (("BENCHWARMERS_ADMIN_TOKEN", saved[4]), ("BENCHWARMERS_REQUIRE_KEYS", saved[7]),
243
- ("BENCH_ALLOWED_ORIGINS", saved[8])):
244
- os.environ.pop(name, None)
245
- if value is not None:
246
- os.environ[name] = value
247
- sys.stderr = saved[5]
248
- dash.shutdown()
249
- gw.shutdown()
250
- print("ok")
251
- return 0
252
-
253
-
254
- if __name__ == "__main__":
255
- sys.exit(main())
1
+ """Login, gateway keys and one port, through the real handlers. No real files.
2
+
3
+ python scripts/test_auth.py
4
+
5
+ metrics/guard.py's self-check covers the pieces (the cookie, key hashing,
6
+ the startup refusal); this covers what the servers do with them, route by
7
+ route: the dashboard behind the login cookie, the gateway's routes behind a
8
+ gateway key on both ports, the open routes (health, login, Connect's
9
+ callback), no auth at all without an admin token, the access log, and both
10
+ servers refusing to listen beyond this machine without a token. Keys, the
11
+ change log and collections point at a temp folder.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import io
17
+ import json
18
+ import os
19
+ import pathlib
20
+ import subprocess
21
+ import sys
22
+ import tempfile
23
+ import threading
24
+ import urllib.error
25
+ import urllib.request
26
+ from http.server import ThreadingHTTPServer
27
+
28
+ ROOT = pathlib.Path(__file__).resolve().parent.parent
29
+ sys.path.insert(0, str(ROOT))
30
+ import serve # noqa: E402
31
+ import gateway # noqa: E402
32
+ import guard # noqa: E402
33
+
34
+ TOKEN = "test-admin-token-0123456789"
35
+
36
+
37
+ class NoRedirect(urllib.request.HTTPRedirectHandler):
38
+ def redirect_request(self, *args, **kwargs):
39
+ return None
40
+
41
+
42
+ OPENER = urllib.request.build_opener(NoRedirect)
43
+
44
+
45
+ def serve_on(handler) -> ThreadingHTTPServer:
46
+ server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
47
+ threading.Thread(target=server.serve_forever, daemon=True).start()
48
+ return server
49
+
50
+
51
+ def call(port: int, path: str, body: dict | None = None, method: str | None = None,
52
+ **headers) -> tuple[int, dict, str]:
53
+ """(status, headers, body text) for one request; HTTP errors are answers too."""
54
+ headers = {"Host": f"127.0.0.1:{port}", **headers}
55
+ data = None
56
+ if body is not None:
57
+ data, headers["Content-Type"] = json.dumps(body).encode(), "application/json"
58
+ req = urllib.request.Request(f"http://127.0.0.1:{port}{path}", data, headers, method=method)
59
+ try:
60
+ with OPENER.open(req, timeout=10) as r:
61
+ return r.status, dict(r.headers), r.read().decode()
62
+ except urllib.error.HTTPError as e:
63
+ return e.code, dict(e.headers), e.read().decode()
64
+
65
+
66
+ def refuses_to_start(script: str) -> str:
67
+ """The startup message when `script` is told to listen beyond this machine without a token."""
68
+ env = {k: v for k, v in os.environ.items() if k != "BENCHWARMERS_ADMIN_TOKEN"}
69
+ env.update(BENCH_BIND="0.0.0.0", BENCH_PORT="8199", BENCH_GATEWAY_PORT="8198", PYTHONIOENCODING="utf-8")
70
+ done = subprocess.run([sys.executable, script], cwd=ROOT, env=env, capture_output=True, text=True,
71
+ encoding="utf-8", errors="replace", timeout=120)
72
+ assert done.returncode != 0, f"{script} started on 0.0.0.0 without a token"
73
+ return done.stderr
74
+
75
+
76
+ def main() -> int:
77
+ for script in ("serve.py", "gateway.py"):
78
+ assert "BENCHWARMERS_ADMIN_TOKEN" in refuses_to_start(script), script
79
+
80
+ dash, gw = serve_on(serve.Handler), serve_on(gateway.Handler)
81
+ d, g = dash.server_port, gw.server_port
82
+ saved = (guard.KEYS, serve.CHANGES_PATH, serve.PORT, gateway.bench_collections.FILE,
83
+ os.environ.pop("BENCHWARMERS_ADMIN_TOKEN", None), sys.stderr, gateway.run_collection,
84
+ os.environ.pop("BENCHWARMERS_REQUIRE_KEYS", None), os.environ.pop("BENCH_ALLOWED_ORIGINS", None))
85
+ with tempfile.TemporaryDirectory() as tmp:
86
+ t = pathlib.Path(tmp)
87
+ try:
88
+ guard.KEYS, serve.CHANGES_PATH, serve.PORT = t / "gateway-keys.json", t / "changes.jsonl", d
89
+ gateway.bench_collections.FILE = t / "collections.json"
90
+ gateway.bench_collections.FILE.write_text(json.dumps({"core": {"members": [], "description": "x"},
91
+ "vision": {"members": [], "description": "x"}}),
92
+ encoding="utf-8")
93
+ # The panel is test_merged's and test_vision's; here only who asked, and whether it ran.
94
+ ran = []
95
+ gateway.run_collection = lambda name, messages, nominated, reply, who: (
96
+ ran.append((name, who)) or "ok", {"seats": []})
97
+
98
+ # NO TOKEN: everything as before -- no login, no key, any bearer ignored.
99
+ assert call(d, "/api/stamp")[0] == 200
100
+ assert call(d, "/login")[:2][0] == 303
101
+ for port in (d, g):
102
+ status, _, text = call(port, "/v1/models", Authorization="Bearer sk-anything")
103
+ assert status == 200 and "bench/core" in text, (port, status, text)
104
+
105
+ # TOKEN SET: the dashboard asks for the login cookie...
106
+ os.environ["BENCHWARMERS_ADMIN_TOKEN"] = TOKEN
107
+ status, headers, _ = call(d, "/")
108
+ assert status == 303 and headers["Location"] == "/login", (status, headers)
109
+ assert call(d, "/api/stamp")[0] == 401
110
+ assert call(d, "/api/theme", {"theme": "nope"})[0] == 401 # writes too
111
+ # Not even which routes exist: the Host, then the login, then the 404.
112
+ assert call(d, "/api/nope", {}, Host="evil.example")[0] == 403
113
+ assert call(d, "/api/nope", {})[0] == 401
114
+ assert call(d, "/fonts/chivo-latin-400-normal.woff2")[0] == 303
115
+ status, _, page = call(d, "/login")
116
+ assert status == 200 and "Admin token" in page
117
+ # ...which only the admin token gets, through the same guard as any write.
118
+ assert call(d, "/login", {"token": "wrong"})[0] == 401
119
+ assert call(d, "/login", {"token": TOKEN}, Origin="http://evil.example")[0] == 403
120
+ status, headers, _ = call(d, "/login", {"token": TOKEN})
121
+ cookie = headers.get("Set-Cookie", "")
122
+ assert status == 200 and "HttpOnly" in cookie and "SameSite=Strict" in cookie, headers
123
+ assert TOKEN not in cookie
124
+ jar = {"Cookie": cookie.split(";", 1)[0]}
125
+ assert call(d, "/api/stamp", **jar)[0] == 200
126
+ assert call(d, "/api/theme", {"theme": "nope"}, **jar)[0] == 400 # in, then the theme rule
127
+ assert call(d, "/api/nope", {}, **jar)[0] == 404
128
+
129
+ # Open without either: health (any Host), and Connect's callback.
130
+ assert call(d, "/health", Host="platform.internal:9")[:3:2] == (200, '{"ok": true}')
131
+ status, _, page = call(d, "/connect/callback?state=nope&code=x")
132
+ assert status == 200 and "unknown or has expired" in page, page
133
+ # ...but not a path merely beginning like it.
134
+ assert call(d, "/connect/callbackx")[0] == 303
135
+
136
+ # Gateway keys, made from the dashboard and shown once.
137
+ assert call(d, "/api/gateway-key", {"name": "chat-ui"})[0] == 401
138
+ status, _, text = call(d, "/api/gateway-key", {"name": "chat-ui"}, **jar)
139
+ key = json.loads(text)["key"]
140
+ assert status == 200 and key not in guard.KEYS.read_text(encoding="utf-8")
141
+ assert key not in serve.CHANGES_PATH.read_text(encoding="utf-8")
142
+ assert key not in serve.settings_page() and "chat-ui" in serve.settings_page()
143
+ bearer = {"Authorization": f"Bearer {key}"}
144
+
145
+ # The gateway's routes, on both ports: the key, never the cookie.
146
+ for port in (d, g):
147
+ status, headers, _ = call(port, "/v1/models")
148
+ assert status == 401 and headers["WWW-Authenticate"].startswith("Bearer"), (port, headers)
149
+ assert call(port, "/v1/models", **jar)[0] == 401, port
150
+ assert call(port, "/v1/models", Authorization="Bearer sk-dummy")[0] == 401, port
151
+ status, _, text = call(port, "/v1/models", **bearer)
152
+ assert status == 200 and json.loads(text)["data"][0]["id"] == "bench/core", (port, text)
153
+ # One port: /bench/verdict reaches the gateway's own validation.
154
+ status, _, text = call(port, "/bench/verdict", {"call": "x"}, **bearer)
155
+ assert status == 400 and "useful" in json.loads(text)["error"]["message"], (port, text)
156
+ assert call(port, "/v1/models", Host="evil.example", **bearer)[0] == 403, port # Host still checked
157
+ # A key never opens the dashboard.
158
+ assert call(d, "/api/stamp", **bearer)[0] == 401
159
+ assert call(d, "/", **bearer)[0] == 303
160
+
161
+ # The access log: route, status, who; never a key, a query or a callback's code.
162
+ log = io.StringIO()
163
+ sys.stderr = log
164
+ call(d, "/v1/models", **bearer)
165
+ call(d, "/api/stamp", **jar)
166
+ call(d, "/connect/callback?state=nope&code=secret-code")
167
+ call(d, "/connect/callback/sid-in-path?code=secret-code")
168
+ sys.stderr = saved[5]
169
+ lines = log.getvalue()
170
+ assert "GET /v1/models 200 chat-ui" in lines and "GET /api/stamp 200 admin" in lines, lines
171
+ assert key not in lines and "secret-code" not in lines and "sid-in-path" not in lines, lines
172
+
173
+ # A key limited to collections and a rate, made on Settings: it sees and calls only
174
+ # those, its name rides on the call, and past its cap it waits.
175
+ status, _, text = call(d, "/api/gateway-key", {"name": "estate", "collections": ["vision"], "rpm": "1"},
176
+ **jar)
177
+ assert status == 200, text
178
+ estate = {"Authorization": f"Bearer {json.loads(text)['key']}"}
179
+ assert call(d, "/api/gateway-key", {"name": "other", "collections": ["nope"]}, **jar)[0] == 400
180
+ assert call(d, "/api/gateway-key", {"name": "other", "rpm": "lots"}, **jar)[0] == 400
181
+ status, _, text = call(g, "/v1/models", **estate)
182
+ assert [m["id"] for m in json.loads(text)["data"]] == ["bench/vision", "bench/vision:merged"], text
183
+ ask = {"messages": [{"role": "user", "content": "hi"}]}
184
+ status, _, text = call(g, "/v1/chat/completions", {"model": "bench/core", **ask}, **estate)
185
+ assert status == 403 and "may call bench/vision" in text, text
186
+ assert call(g, "/v1/chat/completions", {"model": "bench/vision", **ask}, **estate)[0] == 200
187
+ assert call(g, "/v1/chat/completions", {"model": "bench/core", **ask}, **bearer)[0] == 200
188
+ assert ran == [("vision", "estate"), ("core", "chat-ui")], ran
189
+ status, headers, _ = call(g, "/v1/chat/completions", {"model": "bench/vision", **ask}, **estate)
190
+ assert status == 429 and int(headers["Retry-After"]) >= 1, (status, headers)
191
+ page = serve.settings_page()
192
+ assert "estate" in page and "bwk_…" in page and ">vision<" in page, page
193
+
194
+ # CORS for a browser app on an allowed origin, on the gateway's routes only.
195
+ os.environ["BENCH_ALLOWED_ORIGINS"] = "https://estate.example"
196
+ app, evil = {"Origin": "https://estate.example"}, {"Origin": "https://evil.example"}
197
+ asks = {"Access-Control-Request-Method": "POST",
198
+ "Access-Control-Request-Headers": "authorization, content-type, x-stainless-os"}
199
+ for port in (d, g):
200
+ status, headers, _ = call(port, "/v1/chat/completions", method="OPTIONS", **app, **asks)
201
+ assert status == 204 and headers["Access-Control-Allow-Origin"] == "https://estate.example", headers
202
+ assert "Authorization" in headers["Access-Control-Allow-Headers"] and headers["Vary"] == "Origin"
203
+ assert "x-stainless-os" in headers["Access-Control-Allow-Headers"] # the OpenAI JS SDK's
204
+ assert "Access-Control-Allow-Credentials" not in headers
205
+ status, headers, _ = call(port, "/v1/models", method="OPTIONS", **app,
206
+ **{"Access-Control-Request-Headers": "x-ok; not a name"})
207
+ assert status == 204 and headers["Access-Control-Allow-Headers"] == "Authorization, Content-Type"
208
+ status, headers, _ = call(port, "/v1/chat/completions", method="OPTIONS", **evil, **asks)
209
+ assert status == 403 and "Access-Control-Allow-Origin" not in headers, (port, headers)
210
+ status, headers, _ = call(port, "/v1/models", **bearer, **app)
211
+ assert status == 200 and headers["Access-Control-Allow-Origin"] == "https://estate.example"
212
+ assert "Access-Control-Allow-Origin" not in call(port, "/v1/models", **bearer, **evil)[1]
213
+ # The app reads a refusal too; cross-site is fine for it, for nobody else.
214
+ assert call(port, "/v1/models", **app)[1]["Access-Control-Allow-Origin"] == "https://estate.example"
215
+ status, _, text = call(port, "/bench/verdict", {"call": "x"}, **bearer, **app,
216
+ **{"Sec-Fetch-Site": "cross-site"})
217
+ assert status == 400 and "useful" in text, (port, text)
218
+ assert call(port, "/bench/verdict", {"call": "x"}, **bearer, **evil,
219
+ **{"Sec-Fetch-Site": "cross-site"})[0] == 403
220
+ # The dashboard: no preflight, no CORS, same-origin as before.
221
+ assert call(d, "/api/stamp", method="OPTIONS", **app, **asks)[0] == 501
222
+ status, headers, _ = call(d, "/api/theme", {"theme": "nope"}, **jar, **app, **{"Sec-Fetch-Site": "cross-site"})
223
+ assert status == 403 and "Access-Control-Allow-Origin" not in headers, headers
224
+ assert "Access-Control-Allow-Origin" not in call(d, "/api/stamp", **jar, **app)[1]
225
+ del os.environ["BENCH_ALLOWED_ORIGINS"]
226
+
227
+ # Revoked: refused at once.
228
+ assert call(d, "/api/gateway-key/remove", {"name": "chat-ui"}, **jar)[0] == 200
229
+ assert call(d, "/v1/models", **bearer)[0] == 401
230
+ assert call(d, "/api/gateway-key/remove", {"name": "chat-ui"}, **jar)[0] == 400
231
+
232
+ # No admin token, keys required on this machine: the gateway asks, the dashboard does not.
233
+ del os.environ["BENCHWARMERS_ADMIN_TOKEN"]
234
+ os.environ["BENCHWARMERS_REQUIRE_KEYS"] = "1"
235
+ for port in (d, g):
236
+ assert call(port, "/v1/models", Authorization="Bearer sk-anything")[0] == 401, port
237
+ assert call(port, "/v1/models", **estate)[0] == 200, port
238
+ assert call(d, "/api/stamp")[0] == 200
239
+ finally:
240
+ (guard.KEYS, serve.CHANGES_PATH, serve.PORT, gateway.bench_collections.FILE) = saved[:4]
241
+ gateway.run_collection = saved[6]
242
+ for name, value in (("BENCHWARMERS_ADMIN_TOKEN", saved[4]), ("BENCHWARMERS_REQUIRE_KEYS", saved[7]),
243
+ ("BENCH_ALLOWED_ORIGINS", saved[8])):
244
+ os.environ.pop(name, None)
245
+ if value is not None:
246
+ os.environ[name] = value
247
+ sys.stderr = saved[5]
248
+ dash.shutdown()
249
+ gw.shutdown()
250
+ print("ok")
251
+ return 0
252
+
253
+
254
+ if __name__ == "__main__":
255
+ sys.exit(main())