""" Integration tests for the architecture session flow. Tests: create → generate → compare → select → refine. Uses real NVIDIA LLM API calls. **Why fixtures rather than test ordering.** These tests previously stashed the session id on the `pytest` module in `test_create_session` and read it back with `getattr(pytest, "arch_session_id", None)`, skipping when it was absent. A failed create therefore turned the remaining six tests into silent skips, and the suite still reported green. The shared state now lives in module-scoped fixtures: if create or generate fails, every dependent test ERRORs loudly instead. Generation costs several minutes of live LLM time, so `generated_options` runs once per module and is shared by compare / select / refine. """ import json import pytest from tests.integration.conftest import _require_env, require_ok def _parse_sse(body: str) -> tuple[list[dict], list[str]]: """Return (option payloads, event types) parsed from an SSE response body. The generate stream emits `{"type": "agent"|"option"|"done"|"error"}` frames; see `architecture_generation.generate_options_stream`. """ options: list[dict] = [] types: list[str] = [] for line in body.splitlines(): if not line.startswith("data: "): continue event = json.loads(line[len("data: ") :]) types.append(event.get("type")) if event.get("type") == "option": options.append(event["option"]) return options, types @pytest.fixture(scope="module") def created_session(live_client, auth_headers) -> dict: """Create one architecture session shared by every test in this module.""" response = live_client.post( "/architecture/sessions", json={ "project_name": "E-Commerce Platform", "requirements": ( "A scalable e-commerce platform with product catalog, shopping cart, " "payment processing, and order management. Must support 10k concurrent users." ), "constraints": "Budget: $500/month cloud costs. Team: 3 developers. Timeline: 3 months MVP.", }, headers=auth_headers, ) require_ok(response, "POST /architecture/sessions") return response.json() @pytest.fixture(scope="module") def generated_options(live_client, auth_headers, created_session) -> list[dict]: """Generate architecture options once. Several minutes of live LLM time.""" _require_env("NVIDIA_API_KEY") session_id = created_session["id"] response = live_client.post( f"/architecture/sessions/{session_id}/generate", json={"num_options": 3}, headers=auth_headers, ) require_ok(response, f"POST /architecture/sessions/{session_id}/generate") options, types = _parse_sse(response.text) assert "error" not in types, f"generate emitted an error event: {response.text[:800]}" assert options, f"generate produced no option events: {response.text[:800]}" assert "done" in types, f"generate never emitted a done event: {response.text[-800:]}" return options class TestArchitectureSession: """Test architecture session lifecycle with live LLM.""" def test_create_session(self, created_session): """POST /architecture/sessions creates a new session.""" assert created_session["id"] assert created_session["project_name"] == "E-Commerce Platform" assert "10k concurrent users" in created_session["requirements"] def test_get_session(self, live_client, auth_headers, created_session): """GET /architecture/sessions/{id} retrieves session.""" session_id = created_session["id"] response = live_client.get( f"/architecture/sessions/{session_id}", headers=auth_headers, ) require_ok(response, f"GET /architecture/sessions/{session_id}") data = response.json() assert data["id"] == session_id assert data["requirements"] == created_session["requirements"] def test_list_sessions(self, live_client, auth_headers, created_session): """GET /architecture/sessions lists all sessions.""" response = live_client.get("/architecture/sessions", headers=auth_headers) require_ok(response, "GET /architecture/sessions") data = response.json() assert isinstance(data, list) assert created_session["id"] in [s["id"] for s in data], ( "the session created by this module is missing from the listing" ) def test_generate_options(self, generated_options): """POST /architecture/sessions/{id}/generate creates options via SSE. The fixture does the work and asserts the stream shape; this test pins the contract callers depend on - real options, each with an id and name. """ assert len(generated_options) >= 1 for option in generated_options: assert option.get("id"), f"option is missing an id: {option}" assert option.get("name"), f"option is missing a name: {option}" def test_compare_options(self, live_client, auth_headers, created_session, generated_options): """POST /architecture/sessions/{id}/compare compares options.""" _require_env("NVIDIA_API_KEY") session_id = created_session["id"] response = live_client.post( f"/architecture/sessions/{session_id}/compare", headers=auth_headers, ) require_ok(response, f"POST /architecture/sessions/{session_id}/compare") data = response.json() assert "recommendation" in data, f"comparison has no recommendation: {data}" def test_select_option(self, live_client, auth_headers, created_session, generated_options): """POST /architecture/sessions/{id}/select selects an option. Selects an id that actually came out of generate. The old version sent a hardcoded "opt_1" and accepted `status_code in (200, 400, 404, 500)` - an exhaustive set, so it could not fail. """ session_id = created_session["id"] option_id = generated_options[0]["id"] response = live_client.post( f"/architecture/sessions/{session_id}/select", json={"option_id": option_id}, headers=auth_headers, ) require_ok(response, f"POST /architecture/sessions/{session_id}/select") data = response.json() assert data["selected_option"]["id"] == option_id assert data["session_id"] == session_id def test_select_unknown_option_is_404(self, live_client, auth_headers, created_session, generated_options): """Selecting an option that does not exist is rejected, not accepted.""" session_id = created_session["id"] response = live_client.post( f"/architecture/sessions/{session_id}/select", json={"option_id": "opt_does_not_exist"}, headers=auth_headers, ) assert response.status_code == 404, response.text def test_refine_option(self, live_client, auth_headers, created_session, generated_options): """POST /architecture/sessions/{id}/refine refines an option.""" _require_env("NVIDIA_API_KEY") session_id = created_session["id"] option_id = generated_options[0]["id"] response = live_client.post( f"/architecture/sessions/{session_id}/refine", json={ "target_option_id": option_id, "feedback": "Make it more cost-effective and reduce operational complexity.", }, headers=auth_headers, ) require_ok(response, f"POST /architecture/sessions/{session_id}/refine") data = response.json() assert data["refined_option"]["id"] == option_id assert data["iteration"] >= 1