Phase 03 (Story: Chat RAG Answer — happy path):
- app/rag/retriever.py: top-k cosine search + parent-doc selection with
per-doc dedupe and BOR_MAX_CONTEXT_CHARS cap ([…truncated…] marker)
- app/rag/prompts.py: locked persona + HIGH/DEFLECT prompt builders
- app/rag/llm.py: LLMError + chat_stream (turbo, temp 0.4, max 700, stream)
- app/api/chat.py: POST /api/chat SSE — delta* then done{deflected,
sources, suggestions}; query_log row + PLAN §9 per-turn log line;
structured error event on mid-stream failure, JSON 503 when DB down
- frontend: SSE reader, live bubble streaming, source chips -> /sources.html,
red role=alert banner, Send button state that always recovers
- fix(scaffold): [hidden] { display: none !important } — .kb-banner's
display:flex was overriding the hidden attribute (banner always visible)
- tests: unit (retriever/prompts/sse/llm) + integration (real Postgres RAG
turn, query_log, error + 503 paths, mid-turn failures) + Playwright story
suite (grounded answer, log row, raw SSE shape); smoke placeholder test
replaced with the real never-stale-button contract
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
"""Phase 01 smoke E2E: the app boots, serves the local frontend, and a chat
|
|
round-trip never leaves a stale button (answer or error banner, both fine).
|
|
|
|
Run: uv run pytest tests/e2e/test_smoke.py -v
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
from playwright.sync_api import Page, expect
|
|
|
|
|
|
def test_health_endpoint(app_url: str) -> None:
|
|
r = httpx.get(f"{app_url}/api/health", timeout=5)
|
|
assert r.status_code == 200
|
|
assert r.json()["status"] == "ok"
|
|
|
|
|
|
def test_index_page_loads_locally(page: Page, app_url: str) -> None:
|
|
page.goto(app_url)
|
|
assert page.title() == "Brain of Reese"
|
|
assert page.locator(".brand").is_visible()
|
|
# No external (CDN) resources in the document.
|
|
html = page.content()
|
|
assert 'src="http' not in html
|
|
assert 'href="http' not in html.replace('href="http://www.w3.org', "")
|
|
|
|
|
|
def test_chat_roundtrip_never_stale_button(page: Page, app_url: str) -> None:
|
|
page.goto(app_url)
|
|
page.locator("#message-input").fill("hello brain")
|
|
page.locator("#send-btn").click()
|
|
|
|
# User bubble appears first.
|
|
page.locator(".msg.user .bubble").first.wait_for(state="visible", timeout=10_000)
|
|
# Then either a streamed Brain answer (DB up) or an error banner
|
|
# (DB down) — but the turn must always complete.
|
|
page.wait_for_selector(
|
|
".msg.brain .bubble, #kb-banner.is-error",
|
|
state="visible",
|
|
timeout=20_000,
|
|
)
|
|
|
|
# Button is never left stuck: back to "Send" and enabled.
|
|
expect(page.locator("#send-btn")).to_be_enabled(timeout=10_000)
|
|
expect(page.locator("#send-label")).to_have_text("Send", timeout=10_000)
|