Skip to content

Commit b6361d9

Browse files
committed
test: require v1 root endpoint discovery
1 parent 3873fd0 commit b6361d9

4 files changed

Lines changed: 98 additions & 19 deletions

File tree

scripts/smoke_deploy.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,38 @@ def _require_generation_readiness(data: dict) -> None:
459459
)
460460

461461

462+
def _require_v1_root_endpoints(data: dict) -> None:
463+
"""校验 /v1 能力摘要列出主要外部调用入口,方便客户端和网关自发现。"""
464+
endpoints = data.get("endpoints")
465+
_require(isinstance(endpoints, dict), "/v1 missing endpoints")
466+
expected = {
467+
"models": "/v1/models",
468+
"chat_completions": "/v1/chat/completions",
469+
"completions": "/v1/completions",
470+
"responses": "/v1/responses",
471+
"images": "/v1/images/generations",
472+
"image_generations": "/v1/images/generations",
473+
"image_edits": "/v1/images/edits",
474+
"image_variations": "/v1/images/variations",
475+
"audio_transcriptions": "/v1/audio/transcriptions",
476+
"audio_translations": "/v1/audio/translations",
477+
"files": "/v1/files",
478+
"generation_readiness": "/v1/generation-readiness",
479+
"media_cooldowns": "/v1/media-cooldowns",
480+
"gemini_generate": "/v1/gemini/generate",
481+
"gemini_stream": "/v1/gemini/stream",
482+
"gemini_media": "/v1/gemini/media",
483+
"gemini_files": "/v1/gemini/files",
484+
"gemini_gems": "/v1/gemini/gems",
485+
"gemini_jobs": "/v1/gemini/jobs",
486+
"gemini_deep_research_plan": "/v1/gemini/deep-research/plan",
487+
"gemini_deep_research_start": "/v1/gemini/deep-research/start",
488+
"gemini_deep_research_wait": "/v1/gemini/deep-research/wait",
489+
}
490+
for name, path in expected.items():
491+
_require(endpoints.get(name) == path, f"/v1 endpoints.{name} mismatch")
492+
493+
462494
def _require_admin_ui(body: str) -> None:
463495
"""校验管理端首页包含关键诊断入口,避免线上部署后页面仍是旧版本。"""
464496
_require("<title>Gemini API 管理端</title>" in body, "admin UI title is missing")
@@ -1136,7 +1168,7 @@ def _run_smoke_impl(
11361168
else:
11371169
_require(root_status == 200, f"/v1 returned {root_status}")
11381170
_require(root.get("object") == "api.root", "/v1 did not return api.root")
1139-
_require(isinstance(root.get("endpoints"), dict), "/v1 missing endpoints")
1171+
_require_v1_root_endpoints(root)
11401172
_require("x-request-id" in {key.lower(): value for key, value in root_headers.items()}, "/v1 response missing X-Request-ID")
11411173
root_head_status, root_head_body, root_head_headers = _raw_request(
11421174
base_url,

src/gemini_webapi/server/app.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,13 +2338,23 @@ async def v1_root() -> dict[str, Any]:
23382338
"completions": "/v1/completions",
23392339
"responses": "/v1/responses",
23402340
"images": "/v1/images/generations",
2341+
"image_generations": "/v1/images/generations",
2342+
"image_edits": "/v1/images/edits",
2343+
"image_variations": "/v1/images/variations",
23412344
"audio_transcriptions": "/v1/audio/transcriptions",
23422345
"audio_translations": "/v1/audio/translations",
2346+
"files": "/v1/files",
23432347
"generation_readiness": "/v1/generation-readiness",
23442348
"media_cooldowns": "/v1/media-cooldowns",
23452349
"gemini_generate": "/v1/gemini/generate",
23462350
"gemini_stream": "/v1/gemini/stream",
23472351
"gemini_media": "/v1/gemini/media",
2352+
"gemini_files": "/v1/gemini/files",
2353+
"gemini_gems": "/v1/gemini/gems",
2354+
"gemini_jobs": "/v1/gemini/jobs",
2355+
"gemini_deep_research_plan": "/v1/gemini/deep-research/plan",
2356+
"gemini_deep_research_start": "/v1/gemini/deep-research/start",
2357+
"gemini_deep_research_wait": "/v1/gemini/deep-research/wait",
23482358
},
23492359
}
23502360

tests/test_server_endpoints.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -453,22 +453,33 @@ def test_v1_root_is_api_key_protected_capability_summary(self):
453453
self.assertTrue(data["ok"])
454454
self.assertEqual(data["object"], "api.root")
455455
self.assertIn("gemini-3.1-pro", data["models"])
456-
self.assertEqual(
457-
data["endpoints"]["chat_completions"],
458-
"/v1/chat/completions",
459-
)
460-
self.assertEqual(
461-
data["endpoints"]["audio_translations"],
462-
"/v1/audio/translations",
463-
)
464-
self.assertEqual(
465-
data["endpoints"]["generation_readiness"],
466-
"/v1/generation-readiness",
467-
)
468-
self.assertEqual(
469-
data["endpoints"]["media_cooldowns"],
470-
"/v1/media-cooldowns",
471-
)
456+
expected_endpoints = {
457+
"models": "/v1/models",
458+
"chat_completions": "/v1/chat/completions",
459+
"completions": "/v1/completions",
460+
"responses": "/v1/responses",
461+
"images": "/v1/images/generations",
462+
"image_generations": "/v1/images/generations",
463+
"image_edits": "/v1/images/edits",
464+
"image_variations": "/v1/images/variations",
465+
"audio_transcriptions": "/v1/audio/transcriptions",
466+
"audio_translations": "/v1/audio/translations",
467+
"files": "/v1/files",
468+
"generation_readiness": "/v1/generation-readiness",
469+
"media_cooldowns": "/v1/media-cooldowns",
470+
"gemini_generate": "/v1/gemini/generate",
471+
"gemini_stream": "/v1/gemini/stream",
472+
"gemini_media": "/v1/gemini/media",
473+
"gemini_files": "/v1/gemini/files",
474+
"gemini_gems": "/v1/gemini/gems",
475+
"gemini_jobs": "/v1/gemini/jobs",
476+
"gemini_deep_research_plan": "/v1/gemini/deep-research/plan",
477+
"gemini_deep_research_start": "/v1/gemini/deep-research/start",
478+
"gemini_deep_research_wait": "/v1/gemini/deep-research/wait",
479+
}
480+
for name, path in expected_endpoints.items():
481+
with self.subTest(endpoint=name):
482+
self.assertEqual(data["endpoints"][name], path)
472483
self.assertEqual(authorized_slash.status_code, 200)
473484
self.assertEqual(unauthorized_head.status_code, 401)
474485
self.assertEqual(head_root.status_code, 200)

tests/test_smoke_deploy.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@
99
from scripts import smoke_deploy
1010

1111

12+
V1_ROOT_ENDPOINTS = {
13+
"models": "/v1/models",
14+
"chat_completions": "/v1/chat/completions",
15+
"completions": "/v1/completions",
16+
"responses": "/v1/responses",
17+
"images": "/v1/images/generations",
18+
"image_generations": "/v1/images/generations",
19+
"image_edits": "/v1/images/edits",
20+
"image_variations": "/v1/images/variations",
21+
"audio_transcriptions": "/v1/audio/transcriptions",
22+
"audio_translations": "/v1/audio/translations",
23+
"files": "/v1/files",
24+
"generation_readiness": "/v1/generation-readiness",
25+
"media_cooldowns": "/v1/media-cooldowns",
26+
"gemini_generate": "/v1/gemini/generate",
27+
"gemini_stream": "/v1/gemini/stream",
28+
"gemini_media": "/v1/gemini/media",
29+
"gemini_files": "/v1/gemini/files",
30+
"gemini_gems": "/v1/gemini/gems",
31+
"gemini_jobs": "/v1/gemini/jobs",
32+
"gemini_deep_research_plan": "/v1/gemini/deep-research/plan",
33+
"gemini_deep_research_start": "/v1/gemini/deep-research/start",
34+
"gemini_deep_research_wait": "/v1/gemini/deep-research/wait",
35+
}
36+
37+
1238
class FakeHTTPResponse:
1339
def __init__(
1440
self,
@@ -1060,7 +1086,7 @@ def fake_urlopen(request, timeout):
10601086
{
10611087
"ok": True,
10621088
"object": "api.root",
1063-
"endpoints": {"models": "/v1/models"},
1089+
"endpoints": V1_ROOT_ENDPOINTS,
10641090
},
10651091
{"X-Request-ID": "req-v1-root"},
10661092
)
@@ -1209,7 +1235,7 @@ def fake_urlopen(request, timeout):
12091235
{
12101236
"ok": True,
12111237
"object": "api.root",
1212-
"endpoints": {"models": "/v1/models"},
1238+
"endpoints": V1_ROOT_ENDPOINTS,
12131239
},
12141240
{"X-Request-ID": "req-v1-root"},
12151241
)

0 commit comments

Comments
 (0)