Files
cAIc/tests/test_completions.py
T
gramps 90d2cf8326 Rename: jarvisChat → cAIc (product name)
- jarvisChat/JarvisChat/jarvischat → cAIc/cAIc/caic (branded/lower)
- JARVISCHAT_ env vars → CAIC_
- jc- script/config prefix → caic-
- jarvis_rag → caic_rag
- jarvischat.db / volumes → caic.db / caic_*
- AMQP vhost/user jarvischat → caic
- Syslog, loggers, docstrings all updated
- 47 files, zero stale references, 148 tests pass
2026-07-06 19:56:32 -07:00

223 lines
7.6 KiB
Python

import json
import os
from pathlib import Path
import httpx
from fastapi.testclient import TestClient
import app
import config
import db
import routers.completions
from security import SESSIONS, PIN_ATTEMPTS, RATE_EVENTS
def make_client(tmp_path: Path) -> TestClient:
os.environ["CAIC_ADMIN_PIN"] = "1234"
db.DB_PATH = tmp_path / "caic-completions.db"
SESSIONS.clear()
PIN_ATTEMPTS.clear()
RATE_EVENTS.clear()
db.init_db()
return TestClient(app.app, raise_server_exceptions=False)
TEST_API_KEY = "test-sk-caic-completions"
def _auth_headers(extra: dict = None) -> dict:
h = {"Authorization": f"Bearer {TEST_API_KEY}", "Content-Type": "application/json", "Origin": "http://testserver"}
if extra:
h.update(extra)
return h
class _MockStreamResponse:
def __init__(self, lines: list[str]):
self._lines = lines
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
async def aiter_lines(self):
for line in self._lines:
yield line
class _MockAsyncPostResponse:
def __init__(self, status_code=200, json_data=None):
self.status_code = status_code
self._json_data = json_data or {}
def json(self):
return self._json_data
def _stream_json_lines(events: list[dict]) -> list[str]:
return [json.dumps(event) for event in events]
def test_completions_missing_api_key(tmp_path: Path):
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}]},
)
assert resp.status_code == 401
def test_completions_invalid_api_key(tmp_path: Path):
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}]},
headers={"Authorization": "Bearer wrong-key", "Origin": "http://testserver"},
)
assert resp.status_code == 401
def test_completions_no_messages(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
with make_client(tmp_path) as client:
resp = client.post("/v1/chat/completions", json={}, headers=_auth_headers())
assert resp.status_code == 400
def test_completions_empty_messages(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
with make_client(tmp_path) as client:
resp = client.post("/v1/chat/completions", json={"messages": []}, headers=_auth_headers())
assert resp.status_code == 400
def test_completions_no_user_message(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "assistant", "content": "hello"}]},
headers=_auth_headers(),
)
assert resp.status_code == 400
def test_completions_streaming(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
events = _stream_json_lines([
{"choices": [{"delta": {"content": "Hello"}, "logprobs": None}]},
{"choices": [{"delta": {"content": " world"}, "logprobs": None}]},
{"choices": [{"delta": {}, "finish_reason": "stop"}], "usage": {"tokens_per_second": 15.0}},
])
call_count = 0
def stream_stub(self, method, url, json=None, timeout=None):
nonlocal call_count
call_count += 1
return _MockStreamResponse(events)
monkeypatch.setattr(httpx.AsyncClient, "stream", stream_stub)
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}], "stream": True},
headers=_auth_headers(),
)
assert resp.status_code == 200
body = resp.text
assert "data: [DONE]" in body
assert "Hello" in body or "world" in body
assert "chatcmpl-" in body
def test_completions_blocking(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
events = _stream_json_lines([
{"choices": [{"delta": {"content": "Hello world"}, "logprobs": None}]},
{"choices": [{"delta": {}, "finish_reason": "stop"}], "usage": {}},
])
def stream_stub(self, method, url, json=None, timeout=None):
return _MockStreamResponse(events)
monkeypatch.setattr(httpx.AsyncClient, "stream", stream_stub)
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}], "stream": False},
headers=_auth_headers(),
)
assert resp.status_code == 200
data = resp.json()
assert data["object"] == "chat.completion"
assert data["choices"][0]["message"]["content"] == "Hello world"
def test_completions_fim_passthrough(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
fim_data = {"prompt": "def foo():\n ", "suffix": "\n return x", "model": "llama3.1:latest"}
async def mock_post(self, url, json=None, timeout=None):
return _MockAsyncPostResponse(json_data={"choices": [{"text": "pass"}], "usage": {}})
monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
with make_client(tmp_path) as client:
resp = client.post("/v1/chat/completions", json=fim_data, headers=_auth_headers())
assert resp.status_code == 200
assert "choices" in resp.json()
def test_completions_connect_error_stream(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
def broken_stream(self, method, url, json=None, timeout=None):
raise httpx.ConnectError("Connection refused")
monkeypatch.setattr(httpx.AsyncClient, "stream", broken_stream)
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}], "stream": True},
headers=_auth_headers(),
)
assert resp.status_code == 200
assert "connection_error" in resp.text
def test_completions_connect_error_blocking(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
def broken_stream(self, method, url, json=None, timeout=None):
raise httpx.ConnectError("Connection refused")
monkeypatch.setattr(httpx.AsyncClient, "stream", broken_stream)
with make_client(tmp_path) as client:
resp = client.post(
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}], "stream": False},
headers=_auth_headers(),
)
assert resp.status_code == 503
def test_completions_fim_connect_error(tmp_path: Path, monkeypatch):
monkeypatch.setattr(routers.completions, "COMPLETIONS_API_KEY", TEST_API_KEY)
fim_data = {"prompt": "def foo():", "model": "llama3.1:latest"}
def broken_post(self, url, json=None, timeout=None):
raise httpx.ConnectError("Connection refused")
monkeypatch.setattr(httpx.AsyncClient, "post", broken_post)
with make_client(tmp_path) as client:
resp = client.post("/v1/chat/completions", json=fim_data, headers=_auth_headers())
assert resp.status_code == 503