diff --git a/tests/test_chat_api.py b/tests/test_chat_api.py index 2aeacb5..d6cb100 100644 --- a/tests/test_chat_api.py +++ b/tests/test_chat_api.py @@ -138,74 +138,6 @@ def test_autocomplete_invalid_kind_400(client: TestClient) -> None: assert r.status_code == 422 # FastAPI pattern-match → 422 -# --------------------------------------------------------------------------- -# HTML routes (HTMX) -# --------------------------------------------------------------------------- - - -def test_html_chat_orchestrator_renders(client: TestClient) -> None: - r = client.get("/chat") - assert r.status_code == 200 - assert "clare" in r.text - # The hidden cursor input is on the page even with no messages. - assert 'id="chat-after-rowid"' in r.text - - -def test_html_chat_project_renders_after_creation(client: TestClient) -> None: - client.post("/api/v1/projects", json={"name": "alpha"}) - r = client.get("/chat/project/alpha") - assert r.status_code == 200 - assert "alpha" in r.text - - -def test_html_chat_project_missing_404(client: TestClient) -> None: - client.post("/api/v1/projects", json={"name": "alpha"}) # need at least one - r = client.get("/chat/project/nope") - assert r.status_code == 404 - - -def test_html_chat_post_returns_partial(client: TestClient) -> None: - r = client.post( - "/chat/post", - data={"scope": "orchestrator", "scope_ref": "", "body": "/help"}, - ) - assert r.status_code == 200 - assert "chat-msg-user" in r.text - assert "chat-msg-clare" in r.text - # OOB hidden input present so the next poll resumes from the new rowid. - assert 'hx-swap-oob="true"' in r.text - - -def test_html_chat_post_nl_unavailable_for_bare_text_in_project_scope( - client: TestClient, monkeypatch: pytest.MonkeyPatch -) -> None: - # NL fallback fires in project/session scopes only — orchestrator scope - # talks to the managed Claude session instead. - monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) - client.post("/api/v1/projects", json={"name": "alpha"}) - r = client.post( - "/chat/post", - data={"scope": "project", "scope_ref": "alpha", "body": "what is happening"}, - ) - assert r.status_code == 200 - assert "Natural-language parsing unavailable" in r.text - - -def test_html_chat_log_poll_returns_only_new(client: TestClient) -> None: - # Post two messages. - r1 = client.post( - "/api/v1/chat", - json={"scope": "orchestrator", "scope_ref": None, "body": "/status"}, - ) - last = r1.json()["replies"][-1]["rowid"] - r2 = client.post( - "/api/v1/chat", - json={"scope": "orchestrator", "scope_ref": None, "body": "/status"}, - ) - poll = client.get( - "/chat/log", - params={"scope": "orchestrator", "scope_ref": "", "after_rowid": last}, - ) - assert poll.status_code == 200 - # /status posts user + reply → 2 new bubbles since `last`. - assert poll.text.count("chat-msg ") >= 2 +# HTML routes were deleted in R6 — the React SPA owns all browser-facing +# views. JSON API + SSE are the only server-rendered surfaces; everything +# else goes through the catch-all that serves dist/index.html. diff --git a/tests/test_chat_nl.py b/tests/test_chat_nl.py index 737aa6b..c862725 100644 --- a/tests/test_chat_nl.py +++ b/tests/test_chat_nl.py @@ -179,9 +179,11 @@ def test_json_api_posts_preview_for_bare_text( assert "project(s)" in payload["replies"][1]["body"] -def test_html_route_dispatches_after_preview( +def test_json_route_dispatches_after_preview( client: TestClient, monkeypatch: pytest.MonkeyPatch ) -> None: + # The old HTMX /chat/post route was deleted in R6. JSON API covers the + # same flow: NL preview at replies[0], auto-dispatched result at replies[1]. def fake_interpret(text: str, ctx: ScopeCtx, *, client=None) -> nl.ProposedAction: return nl.ProposedAction( slash="/status", explanation="fleet check", confidence=1.0, @@ -190,10 +192,11 @@ def test_html_route_dispatches_after_preview( monkeypatch.setattr("clare.web.chat.nl.interpret", fake_interpret) client.post("/api/v1/projects", json={"name": "alpha"}) r = client.post( - "/chat/post", - data={"scope": "project", "scope_ref": "alpha", "body": "any news?"}, + "/api/v1/chat", + json={"scope": "project", "scope_ref": "alpha", "body": "any news?"}, ) - assert r.status_code == 200 - assert "Interpreted as" in r.text - assert "project(s)" in r.text - assert "session(s)" in r.text + assert r.status_code == 201 + payload = r.json() + assert payload["replies"][0]["meta"]["kind"] == "nl_preview" + assert "project(s)" in payload["replies"][1]["body"] + assert "session(s)" in payload["replies"][1]["body"] diff --git a/tests/test_chat_stream.py b/tests/test_chat_stream.py index 2d34560..1f8333e 100644 --- a/tests/test_chat_stream.py +++ b/tests/test_chat_stream.py @@ -87,11 +87,18 @@ def _collect_until(resp, marker: str, timeout: float = 3.0) -> str: def test_stream_yields_existing_backlog(client: TestClient) -> None: - """Initial flush: messages already past after_rowid arrive immediately.""" - client.post( + """Initial flush: rowids already past after_rowid arrive immediately. + + Post-R6 the SSE payload is `{"new_rowids": [...]}` JSON, not HTML. + The React client uses the event as a wake-up signal and refetches via + `GET /api/v1/chat`. + """ + r = client.post( "/api/v1/chat", json={"scope": "orchestrator", "scope_ref": None, "body": "hello-backlog"}, ) + user_rowid = r.json()["user_message"]["rowid"] + marker = f"{user_rowid}" with client.stream( "GET", "/chat/stream", @@ -101,9 +108,10 @@ def test_stream_yields_existing_backlog(client: TestClient) -> None: }, ) as resp: assert resp.status_code == 200 - text = _collect_until(resp, "hello-backlog", timeout=2.0) + text = _collect_until(resp, marker, timeout=2.0) assert "event: chat" in text - assert "hello-backlog" in text + assert "new_rowids" in text + assert marker in text def test_stream_pushes_new_message(client: TestClient) -> None: @@ -115,16 +123,18 @@ def test_stream_pushes_new_message(client: TestClient) -> None: ) baseline = r0.json()["user_message"]["rowid"] - # Background poster: fires a message a moment after we start streaming. + target_rowid: list[int] = [] + def post_later() -> None: time.sleep(0.3) - client.post( + r = client.post( "/api/v1/chat", json={ "scope": "orchestrator", "scope_ref": None, "body": "live-update-marker", }, ) + target_rowid.append(r.json()["user_message"]["rowid"]) poster = threading.Thread(target=post_later, daemon=True) poster.start() @@ -138,8 +148,10 @@ def test_stream_pushes_new_message(client: TestClient) -> None: }, ) as resp: assert resp.status_code == 200 - text = _collect_until(resp, "live-update-marker", timeout=4.0) + poster.join(timeout=2.0) + assert target_rowid, "background poster failed to deliver" + text = _collect_until(resp, str(target_rowid[0]), timeout=4.0) - poster.join(timeout=1.0) - assert "live-update-marker" in text assert "event: chat" in text + assert "new_rowids" in text + assert str(target_rowid[0]) in text