"""HTTP tests for the AI Chat scratchpad. Most of this file used to be about the shared demo key: an access gate on a "power user" email allowlist, and a pinning rule that stopped a public visitor reaching paid models on a server-funded key. M2 removed the hosted deployment those defended, so the rules they tested no longer exist to be tested. See `planning/archive/milestone-reports/M2-*` for the accounting. What remains is what the page still does: stream a reply from the configured model, honour a system prompt and a per-request model override, and refuse a conversation too large to send. python -m pytest tests/test_chat.py -v """ import pytest from fastapi import Depends from fastapi.testclient import TestClient from app import auth, models from app.database import Base, SessionLocal, engine, get_db from app.main import app from app.routers import chat class FakeProvider: """Records what it was constructed with, then streams a fixed reply. It stands in for the real egress point, so asserting on `last_endpoint` and `last_model` is asserting on exactly what would have gone over the wire. There is no `last_key` any more: the provider takes no API key, because Ollama does not use one. """ last_usage = None last_model = None last_endpoint = None last_messages = None def __init__(self, endpoint_url, model, api_mode="chat", read_timeout=None): FakeProvider.last_model = model FakeProvider.last_endpoint = endpoint_url async def chat(self, messages, *, temperature, max_tokens): FakeProvider.last_messages = messages yield ("reasoning", "hmm") yield ("text", "Hello back.") @pytest.fixture() def client(monkeypatch): Base.metadata.create_all(bind=engine) setup = SessionLocal() user = models.User(is_guest=False) setup.add(user) setup.flush() setup.add(models.Settings(user_id=user.id, model="test-model")) setup.commit() user_id = user.id setup.close() monkeypatch.setattr(chat, "OpenAICompatibleProvider", FakeProvider) def _current_user(db=Depends(get_db)): return db.get(models.User, user_id) app.dependency_overrides[auth.get_current_user] = _current_user c = TestClient(app) try: yield c finally: app.dependency_overrides.clear() Base.metadata.drop_all(bind=engine) def _send(client, **body): payload = {"messages": [{"role": "user", "content": "hi"}]} payload.update(body) return client.post("/api/chat/stream", json=payload) def test_the_page_streams_a_reply(client): resp = _send(client) assert resp.status_code == 200, resp.text assert '"type": "reasoning"' in resp.text assert "Hello back." in resp.text assert '"type": "done"' in resp.text assert FakeProvider.last_messages == [{"role": "user", "content": "hi"}] def test_it_uses_the_configured_endpoint_and_model(client): _send(client) assert FakeProvider.last_model == "test-model" # The default from `models.Settings`, and the only kind of address the # endpoint policy allows without configuration. assert FakeProvider.last_endpoint == "http://localhost:11434/v1" def test_system_prompt_and_model_override_are_honoured(client): resp = client.post("/api/chat/stream", json={ "messages": [ {"role": "system", "content": "Be terse."}, {"role": "user", "content": "hi"}, ], "model": "some-other-model", }) assert resp.status_code == 200, resp.text assert FakeProvider.last_model == "some-other-model" assert FakeProvider.last_messages[0] == {"role": "system", "content": "Be terse."} def test_a_request_with_no_model_anywhere_is_refused(client): db = SessionLocal() try: settings = db.query(models.Settings).first() settings.model = "" db.commit() finally: db.close() assert _send(client).status_code == 400 def test_oversized_conversation_is_refused(client): huge = "x" * 90_000 resp = client.post("/api/chat/stream", json={ "messages": [{"role": "user", "content": huge} for _ in range(5)], }) assert resp.status_code == 413, resp.text