ASE-GLM / tests /test_api.py
Bit-Trading-Company's picture
CI deploy ad5ff8be
9a95b72 verified
Raw History Blame Contribute Delete
4.39 kB
"""The HTTP surface, exercised with no network and no credentials."""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from server import catalog
from server.main import app
@pytest.fixture(autouse=True)
def no_credentials(monkeypatch):
for var in ("HF_TOKEN", "HUGGING_FACE_HUB_TOKEN", "SPACE_ID",
"ASE_ALLOW_SPACE_CREDITS"):
monkeypatch.delenv(var, raising=False)
# The suite must never pick up the developer's real key.txt.
monkeypatch.setenv("ASE_KEY_FILE", "/nonexistent/ase-glm-test-key")
@pytest.fixture
def client():
return TestClient(app)
def test_health(client):
assert client.get("/api/health").json()["ok"] is True
def test_config_lists_the_models_and_the_billing_mode(client):
body = client.get("/api/config").json()
ids = [m["id"] for m in body["models"]]
assert "zai-org/GLM-5.3" in ids
assert body["defaultModel"] == catalog.DEFAULT_MODEL
assert body["auth"]["billsViewer"] is False
assert "token" not in body["auth"]
def test_every_catalog_model_is_well_formed():
for m in catalog.MODELS:
j = m.to_json()
assert "/" in j["id"], "an id must be owner/name to route"
assert j["label"] and j["vendor"]
assert j["providers"], f"{j['id']} has no route"
assert j["contextTokens"] is None or j["contextTokens"] > 0
def sse_events(response) -> list[tuple[str, str]]:
out, name, data = [], "message", []
for line in response.text.split("\n"):
if line.startswith("event:"):
name = line[6:].strip()
elif line.startswith("data:"):
data.append(line[5:].lstrip(" "))
elif not line.strip() and data:
out.append((name, "\n".join(data)))
name, data = "message", []
if data:
out.append((name, "\n".join(data)))
return out
def test_a_missing_credential_is_reported_in_the_stream_not_as_a_4xx(client):
# The client renders errors into the transcript. A 4xx would show as a
# transport failure somewhere the reader is not looking.
r = client.post("/api/chat", json={
"model": "zai-org/GLM-5.3",
"messages": [{"role": "user", "content": "hi"}],
})
assert r.status_code == 200
events = sse_events(r)
assert events[0][0] == "error"
assert "credential" in events[0][1].lower()
def test_an_unknown_model_is_refused_before_any_upstream_call(client):
r = client.post("/api/chat", json={
"model": "../../etc/passwd",
"messages": [{"role": "user", "content": "hi"}],
})
assert sse_events(r)[0][0] == "error"
assert "Unknown model" in sse_events(r)[0][1]
@pytest.mark.parametrize("body", [
{"model": "zai-org/GLM-5.3"},
{"model": "zai-org/GLM-5.3", "messages": []},
{"model": "zai-org/GLM-5.3", "messages": "not a list"},
])
def test_a_request_with_no_messages_is_refused(client, body):
assert sse_events(client.post("/api/chat", json=body))[0][0] == "error"
def test_model_insight_rejects_a_model_not_in_the_catalog(client):
r = client.get("/api/model", params={"id": "evil/model"})
assert r.status_code == 400
def test_an_unknown_api_path_is_a_404_not_the_index_page(client):
# The SPA catch-all matches anything the declared routes did not. If it
# answers an /api path, a typo'd endpoint returns 200 full of HTML and the
# client reports a JSON parse error a long way from the cause.
r = client.get("/api/does-not-exist")
assert r.status_code == 404
assert "<html" not in r.text.lower()
def test_a_non_api_path_still_falls_through_to_the_app(client):
# Only reachable once the bundle has been built; skip rather than fail in a
# bare checkout, since CI builds it before the deploy either way.
r = client.get("/some/client/route")
assert r.status_code in (200, 404)
def test_the_page_is_not_cached(client):
"""A cached index.html keeps pointing at the previous bundle.
The assets are content-hashed and may cache forever; the page that names
them may not, or a deploy is invisible until the browser feels like
re-fetching — which reads as "the deploy did nothing".
"""
for path in ("/", "/anything"):
r = client.get(path)
if r.status_code != 200:
continue
assert "no-cache" in r.headers.get("cache-control", "")