Files
brain-of-reese/tests/integration/test_api.py
T

131 lines
4.2 KiB
Python

"""Integration tests: HTTP API surface (no database required)."""
from __future__ import annotations
import json
import pytest
from app.config import get_settings
def test_health_reports_ok(client) -> None:
r = client.get("/api/health")
assert r.status_code == 200
body = r.json()
assert body["status"] == "ok"
assert body["db"] in {"up", "down"}
assert body["version"]
def test_suggestions_returns_list(client) -> None:
r = client.get("/api/suggestions")
assert r.status_code == 200
suggestions = r.json()["suggestions"]
assert isinstance(suggestions, list)
assert len(suggestions) >= 3
assert all(isinstance(s, str) and s.strip() for s in suggestions)
def test_suggestions_honors_bor_suggestions_env_override(monkeypatch) -> None:
"""GET /api/suggestions reflects the BOR_SUGGESTIONS JSON env override."""
from fastapi.testclient import TestClient
from app.main import create_app
override = [
"How do I back up with Borg?",
"How is my K3S cluster set up?",
"How do I deploy a service?",
"What proxy fronts reeseapps.com?",
]
get_settings.cache_clear()
try:
monkeypatch.setenv("BOR_SUGGESTIONS", json.dumps(override))
fresh_client = TestClient(create_app())
finally:
get_settings.cache_clear()
r = fresh_client.get("/api/suggestions")
assert r.status_code == 200
assert r.json() == {"suggestions": override}
@pytest.mark.parametrize(
("path", "marker"),
[
("/", "Brain of Reese"),
("/sources.html", "Knowledge base"),
("/document.html", "Brain of Reese"), # phase 10: viewer page
("/login.html", "Sign in"), # phase 16: admin sign-in page
],
)
def test_html_pages_served_locally_no_cdn(client, path: str, marker: str) -> None:
"""No-CDN check (PLAN §7.3, re-verified on BOTH pages in phase 07 and
on the viewer page in phase 10): each page is served by FastAPI and
references only same-origin assets (no https:// script/link tags)."""
r = client.get(path)
assert r.status_code == 200
assert marker in r.text
assert 'src="https://' not in r.text
assert 'href="https://' not in r.text
def test_styles_and_js_served(client) -> None:
assert client.get("/assets/styles.css").status_code == 200
assert client.get("/assets/app.js").status_code == 200
assert client.get("/assets/sources.js").status_code == 200
assert client.get("/assets/markdown.js").status_code == 200 # phase 10: shared renderer
assert client.get("/assets/document.js").status_code == 200 # phase 10: viewer page
assert client.get("/assets/login.js").status_code == 200 # phase 16: login page
assert client.get("/assets/document-modal.js").status_code == 200 # phase 26: modal module
# Emoji code points banned from UI chrome (phase 08): the pictograph
# blocks, VS-16/ZWJ, plus the exact glyphs the old light theme used
# (🧠 🧑 👋 📂 ⚠).
_EMOJI_GLYPHS = "\U0001F9E0\U0001F9D1\U0001F44B\U0001F4C2\u26A0"
def _find_emoji(text: str) -> list[str]:
hits: list[str] = []
for ch in text:
cp = ord(ch)
if (
0x1F300 <= cp <= 0x1FAFF
or 0x2600 <= cp <= 0x27BF
or 0x2B00 <= cp <= 0x2BFF
or cp in (0xFE0F, 0x200D)
or ch in _EMOJI_GLYPHS
):
hits.append(ch)
return hits
@pytest.mark.parametrize(
"path",
[
"/",
"/sources.html",
"/document.html",
"/login.html", # phase 16
"/assets/app.js",
"/assets/sources.js",
"/assets/markdown.js",
"/assets/document.js",
"/assets/login.js", # phase 16
"/assets/document-modal.js", # phase 26: the document modal module
"/assets/styles.css",
],
)
def test_ui_chrome_has_no_emoji(client, path: str) -> None:
"""Permanent regression guard (phase 08): the UI chrome — all pages,
the JS that renders it, and the stylesheet — is emoji-free."""
r = client.get(path)
assert r.status_code == 200
assert _find_emoji(r.text) == [], f"emoji found in {path}: {_find_emoji(r.text)!r}"
def test_chat_requires_message(client) -> None:
r = client.post("/api/chat", json={"message": ""})
assert r.status_code == 422