atlas-iac/testing/tests/test_hermes_batch_api.py

104 lines
3.8 KiB
Python
Raw Normal View History

"""Verify the batch boundary fails closed before private source can be routed."""
import json
from urllib.error import URLError
import pytest
from services.hermes.scripts import batch_api as api
@pytest.fixture
def payload():
return {"model": "qwen3.5:9b", "prompt": "Return a JSON object containing ok=true.",
"stream": False, "think": False, "format": "json",
"options": {"num_ctx": 16384, "num_predict": 1024, "temperature": 0,
"top_p": 1, "top_k": 20, "seed": 42}}
@pytest.fixture
def upstream(monkeypatch):
calls = []
def respond(path, payload=None, timeout=10):
calls.append((path, payload))
if path == "/api/version":
return {"version": api.VERSION}
if path == "/api/tags":
return {"models": [{"name": n, "digest": d, "size": 123} for n, d in api.PINS.items()]}
return {"model": payload["model"], "done": True, "done_reason": "stop",
"response": '{"ok":true}', "thinking": "private internal reasoning", "context": [1]}
monkeypatch.setattr(api, "_request", respond)
return calls
def test_pinned_result_records_actual_settings_without_raw_thinking(payload, upstream):
status, result = api.generate(json.dumps(payload))
assert status == 200
assert "thinking" not in result and "context" not in result
assert result["batch_provenance"]["model_digest"] == api.PINS[payload["model"]]
assert result["batch_provenance"]["options"]["num_gpu"] == 0
assert upstream[-1][0] == "/api/generate"
@pytest.mark.parametrize("change", [
{"model": "qwen3.6:27b-cloud"}, {"model": "qwen3.6:latest"},
{"think": "high"}, {"stream": True}, {"images": ["anything"]},
{"tools": []}, {"prompt": "x" * 16384}, {"format": "text"},
{"options": {"num_gpu": 999}}, {"prompt": None},
])
def test_rejected_requests_do_not_reach_upstream(payload, upstream, change):
payload.update(change)
assert api.generate(json.dumps(payload))[0] == 400
assert upstream == []
@pytest.mark.parametrize("key,value", [("temperature", float("nan")), ("num_predict", -1),
("num_ctx", 262144), ("seed", True)])
def test_invalid_options_do_not_reach_upstream(payload, upstream, key, value):
payload["options"][key] = value
assert api.generate(json.dumps(payload))[0] == 400
assert upstream == []
@pytest.mark.parametrize("mode", ["version", "digest", "missing", "outage"])
def test_upstream_identity_failure_never_sends_source(payload, monkeypatch, mode):
calls = []
def broken(path, *args, **kwargs):
calls.append(path)
if mode == "outage":
raise URLError("unavailable")
if path == "/api/version":
return {"version": "changed" if mode == "version" else api.VERSION}
return {"models": [] if mode == "missing" else [{"name": n, "digest": "wrong"} for n in api.PINS]}
monkeypatch.setattr(api, "_request", broken)
assert api.generate(json.dumps(payload))[0] == 503
assert "/api/generate" not in calls
assert api.models_response()[0] == 503
def test_busy_request_and_truncated_generation_are_not_success(payload, upstream, monkeypatch):
api._inference.acquire()
try:
assert api.generate(json.dumps(payload))[0] == 429
assert not upstream
finally:
api._inference.release()
original = api._request
def truncated(path, *args, **kwargs):
result = original(path, *args, **kwargs)
if path == "/api/generate":
result["done_reason"] = "length"
return result
monkeypatch.setattr(api, "_request", truncated)
assert api.generate(json.dumps(payload))[0] == 422
def test_redirects_are_never_followed():
assert api.NoRedirect().redirect_request(None, None, 307, "redirect", {}, "https://external.invalid") is None