Spaces:
Paused
Paused
File size: 6,210 Bytes
8c1b9fe | 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 | from __future__ import annotations
import io
import json
import wave
import pytest
from auralynq.pipeline import build_index
from auralynq.serving.app import create_app
from fastapi.testclient import TestClient
@pytest.fixture
def client(corpus_dir):
build_index(corpus_dir)
from auralynq.agent import runner
runner._CACHE.clear()
app = create_app()
return TestClient(app)
def test_health(client):
r = client.get("/health")
assert r.status_code == 200
body = r.json()
assert body["status"] == "ok"
assert any(p["subsystem"] == "llm" for p in body["providers"])
assert "X-Request-ID" in r.headers
def test_metrics(client):
client.get("/health")
r = client.get("/metrics")
assert r.status_code == 200
assert "requests_total" in r.json()
def test_query_grounded(client):
r = client.post("/query", json={"question": "What is the capital of France?"})
assert r.status_code == 200
body = r.json()
assert body["answer"]
assert body["citations"]
assert body["request_id"]
assert {"name"} # trace present
assert any(sp["name"] == "citation_validator" for sp in body["trace"])
def test_query_stream_sse(client):
with client.stream("POST", "/query/stream", json={"question": "What is PathRAG?"}) as r:
assert r.status_code == 200
events = [line for line in r.iter_lines() if line]
assert any("final" in e for e in events)
def test_ingest_upload(client):
content = b"# Upload\n\nQdrant stores dense and sparse vectors for hybrid search."
r = client.post("/ingest", files={"file": ("note.md", content, "text/markdown")})
assert r.status_code == 200
assert r.json()["documents"] >= 1
def test_ingest_rejects_unsupported_file_type(client):
r = client.post(
"/ingest", files={"file": ("payload.exe", b"MZ\x90\x00", "application/octet-stream")}
)
assert r.status_code == 415
assert r.json()["error"]["code"] == "unsupported_file_type"
def test_ingest_disabled_when_allow_uploads_false(corpus_dir, monkeypatch):
monkeypatch.setenv("AURALYNQ_ALLOW_UPLOADS", "false")
from auralynq.config import reload_settings
reload_settings()
build_index(corpus_dir)
app = create_app()
c = TestClient(app)
r = c.post("/ingest", files={"file": ("note.md", b"# Hi", "text/markdown")})
assert r.status_code == 403
assert r.json()["error"]["code"] == "uploads_disabled"
def test_voice_endpoint(client, tmp_path):
audio = io.BytesIO()
with wave.open(audio, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(16000)
wf.writeframes(b"\x00\x00" * 16000)
# No sidecar => empty transcript, but endpoint must respond gracefully.
r = client.post("/voice", files={"file": ("q.wav", audio.getvalue(), "audio/wav")})
assert r.status_code == 200
assert "answer" in r.json()
def test_eval_report_pending(client):
r = client.get("/eval/report")
assert r.status_code == 200
def test_ws_voice_roundtrip(client):
with client.websocket_connect("/ws/voice") as ws:
ws.send_bytes(b"\x00\x00" * 100)
ack = ws.receive_json()
assert ack["type"] == "ack"
ws.send_text(json.dumps({"action": "end"}))
msg = ws.receive_json()
assert msg["type"] == "transcript"
final = ws.receive_json()
assert final["type"] == "final"
ws.close()
def test_version_endpoint(client):
r = client.get("/version")
assert r.status_code == 200
body = r.json()
assert body["name"] == "auralynq"
assert body["version"]
assert body["api"] == "v1"
def test_ready_endpoint_reports_index(client):
# the `client` fixture indexes a corpus, so the service is ready
r = client.get("/ready")
assert r.status_code == 200
assert r.json()["ready"] is True
assert r.json()["vectors"] >= 1
def _assert_error_envelope(body: dict, status_code: int, r) -> None:
assert set(body.keys()) == {"error"}
err = body["error"]
assert {"code", "message", "details", "trace_id"} <= set(err)
assert isinstance(err["code"], str) and err["code"]
assert isinstance(err["message"], str)
assert isinstance(err["details"], dict)
# trace_id matches the X-Request-ID response header for this same request
assert err["trace_id"] == r.headers.get("X-Request-ID", err["trace_id"])
def test_error_envelope_on_auralynq_error(client):
# /corpus/clear/confirm with a wrong phrase raises AuralynqError("wrong_phrase", ...)
r = client.post("/corpus/clear/confirm", json={"phrase": "not the phrase"})
assert r.status_code == 400
_assert_error_envelope(r.json(), 400, r)
assert r.json()["error"]["code"] == "wrong_phrase"
def test_error_envelope_on_404(client):
r = client.get("/this-route-does-not-exist")
assert r.status_code == 404
_assert_error_envelope(r.json(), 404, r)
assert r.json()["error"]["code"] == "http_error"
def test_error_envelope_on_validation_error(client):
# `question` is required by QueryRequest — omitting it triggers a 422.
r = client.post("/query", json={})
assert r.status_code == 422
body = r.json()
_assert_error_envelope(body, 422, r)
assert body["error"]["code"] == "validation_error"
assert body["error"]["details"]["errors"] # pydantic error list preserved
def test_error_envelope_on_unhandled_exception(corpus_dir, monkeypatch):
build_index(corpus_dir)
from auralynq.rag import strategy_registry
def _boom(*args, **kwargs):
raise RuntimeError("synthetic failure for error-envelope test")
monkeypatch.setattr(strategy_registry.get_registry().__class__, "run", _boom)
# raise_server_exceptions=False so the 500 handler's JSON is returned to us
# instead of the exception being re-raised into the test (TestClient's
# debugging default).
app = create_app()
non_raising_client = TestClient(app, raise_server_exceptions=False)
r = non_raising_client.post("/query", json={"question": "hi"})
assert r.status_code == 500
body = r.json()
_assert_error_envelope(body, 500, r)
assert body["error"]["code"] == "internal_error"
|