"""Integration tests for POST /api/v5/checkpoint/extract. Tests use MockAdapter's JSON-mode path (registry._MOCK_JSON_RESPONSE), which returns a deterministic canned blob when the caller passes response_format={"type": "json_object"}. The extract endpoint always requests JSON mode, so every test hits the canned response. """ def _sample_markdown() -> str: return """# Design: envdiff CLI ## Objective Build a stdlib-only CLI that compares two .env files. ## Decisions - Use argparse, not click - Single file, not a package ## Assumptions - Python 3.11+ is available ```python def main(): print("hello") ``` """ def test_extract_happy_path_with_mock(client): """Extract endpoint returns canned mock fields when use_mock=True.""" r = client.post( "/api/v5/checkpoint/extract", json={"content": _sample_markdown(), "use_mock": True}, ) assert r.status_code == 200, r.text data = r.json() # MockAdapter JSON mode returns the canned _MOCK_JSON_RESPONSE in # registry.py — these are the exact values defined there. assert data["title"] == "Mock Checkpoint" assert data["summary"].startswith("Mock summary") assert "Mock decision from provider" in data["decisions"] assert "Mock assumption from provider" in data["assumptions"] # Tasks are now structured objects; a plain string from the mock # becomes {"text": "Mock task from provider"}. assert any( (t.get("text") if isinstance(t, dict) else t) == "Mock task from provider" for t in data["tasks"] ) assert "Mock open question from provider" in data["open_questions"] assert "MockEntity" in data["entities"] assert len(data["artifacts"]) == 1 assert data["artifacts"][0]["label"] == "Mock artifact" def test_extract_default_path_returns_shape(client): """Default path (no use_mock flag) returns a valid CheckpointExtractResponse shape regardless of which provider answers. In a test env with no API keys this hits MockAdapter via the allow_mock=True fallback; in a dev env with real keys it hits the real provider and returns real extracted fields. Either way the response must be well-shaped. """ r = client.post( "/api/v5/checkpoint/extract", json={"content": _sample_markdown()}, ) assert r.status_code == 200, r.text data = r.json() # Response shape: every field present, strings are strings, lists are lists. assert isinstance(data["title"], str) assert isinstance(data["objective"], str) assert isinstance(data["summary"], str) assert isinstance(data["decisions"], list) assert isinstance(data["assumptions"], list) assert isinstance(data["tasks"], list) assert isinstance(data["open_questions"], list) assert isinstance(data["entities"], list) assert isinstance(data["artifacts"], list) # Something must have been extracted from the sample — the title must be # non-empty and at least one decision should appear since the sample has # a "## Decisions" section with two bullet points. assert data["title"].strip() != "" assert len(data["decisions"]) > 0 def test_extract_rejects_empty_content(client): """Empty content is a 422 validation error.""" r = client.post( "/api/v5/checkpoint/extract", json={"content": " "}, ) assert r.status_code == 422, r.text def test_extract_rejects_oversized_content(client): """Content exceeding 200000 character cap is a 422 validation error.""" r = client.post( "/api/v5/checkpoint/extract", json={"content": "x" * 300_000}, ) assert r.status_code == 422, r.text