import base64 import pytest from httpx import AsyncClient pytestmark = pytest.mark.asyncio @pytest.fixture async def test_session(client: AsyncClient, auth_headers: dict, test_tree: dict): """Create a test session from the test tree.""" response = await client.post( "/api/v1/sessions", json={"tree_id": test_tree["id"]}, headers=auth_headers, ) assert response.status_code == 201, f"Failed to create session: {response.text}" return response.json() # --- Create --- async def test_create_text_snippet(client: AsyncClient, auth_headers: dict, test_session: dict): """Create a text snippet supporting data item — returns 201.""" response = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "Error log", "data_type": "text_snippet", "content": "NullReferenceException at line 42", }, headers=auth_headers, ) assert response.status_code == 201 data = response.json() assert data["label"] == "Error log" assert data["data_type"] == "text_snippet" assert data["content"] == "NullReferenceException at line 42" assert data["sort_order"] == 1 assert data["session_id"] == test_session["id"] async def test_create_screenshot(client: AsyncClient, auth_headers: dict, test_session: dict): """Create a screenshot supporting data item — returns 201.""" # Small valid base64 content (a tiny PNG-like payload) small_content = base64.b64encode(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100).decode() response = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "Error screenshot", "data_type": "screenshot", "content": small_content, "content_type": "image/png", }, headers=auth_headers, ) assert response.status_code == 201 data = response.json() assert data["label"] == "Error screenshot" assert data["data_type"] == "screenshot" assert data["content_type"] == "image/png" # --- List --- async def test_list_items_in_sort_order(client: AsyncClient, auth_headers: dict, test_session: dict): """List returns items ordered by sort_order.""" # Create 3 items for i in range(3): resp = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": f"Item {i}", "data_type": "text_snippet", "content": f"Content {i}", }, headers=auth_headers, ) assert resp.status_code == 201 response = await client.get( f"/api/v1/sessions/{test_session['id']}/supporting-data", headers=auth_headers, ) assert response.status_code == 200 items = response.json() assert len(items) == 3 assert items[0]["label"] == "Item 0" assert items[1]["label"] == "Item 1" assert items[2]["label"] == "Item 2" assert items[0]["sort_order"] < items[1]["sort_order"] < items[2]["sort_order"] # --- Delete --- async def test_delete_item(client: AsyncClient, auth_headers: dict, test_session: dict): """Delete removes the item.""" create_resp = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "To delete", "data_type": "text_snippet", "content": "Will be removed", }, headers=auth_headers, ) assert create_resp.status_code == 201 item_id = create_resp.json()["id"] delete_resp = await client.delete( f"/api/v1/sessions/{test_session['id']}/supporting-data/{item_id}", headers=auth_headers, ) assert delete_resp.status_code == 204 # Verify it's gone list_resp = await client.get( f"/api/v1/sessions/{test_session['id']}/supporting-data", headers=auth_headers, ) assert list_resp.status_code == 200 assert len(list_resp.json()) == 0 # --- Validation --- async def test_exceed_20_item_limit(client: AsyncClient, auth_headers: dict, test_session: dict): """Cannot exceed 20 items per session — returns 400.""" for i in range(20): resp = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": f"Item {i}", "data_type": "text_snippet", "content": f"Content {i}", }, headers=auth_headers, ) assert resp.status_code == 201, f"Failed creating item {i}: {resp.text}" # 21st should fail response = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "One too many", "data_type": "text_snippet", "content": "Should fail", }, headers=auth_headers, ) assert response.status_code == 400 assert "20" in response.json()["detail"] async def test_screenshot_exceeds_2mb(client: AsyncClient, auth_headers: dict, test_session: dict): """Screenshot over 2MB raw (base64 decoded) — returns 400.""" # Create content that decodes to > 2MB large_raw = b"\x00" * (2 * 1024 * 1024 + 1) # 2MB + 1 byte large_b64 = base64.b64encode(large_raw).decode() response = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "Large screenshot", "data_type": "screenshot", "content": large_b64, }, headers=auth_headers, ) assert response.status_code == 400 assert "2MB" in response.json()["detail"] async def test_text_snippet_over_50k_chars(client: AsyncClient, auth_headers: dict, test_session: dict): """Text snippet over 50,000 characters — returns 400.""" response = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "Huge text", "data_type": "text_snippet", "content": "x" * 50_001, }, headers=auth_headers, ) assert response.status_code == 400 assert "50000" in response.json()["detail"] # --- Update --- async def test_patch_update_label(client: AsyncClient, auth_headers: dict, test_session: dict): """PATCH to update label returns updated item.""" create_resp = await client.post( f"/api/v1/sessions/{test_session['id']}/supporting-data", json={ "label": "Original label", "data_type": "text_snippet", "content": "Some content", }, headers=auth_headers, ) assert create_resp.status_code == 201 item_id = create_resp.json()["id"] patch_resp = await client.patch( f"/api/v1/sessions/{test_session['id']}/supporting-data/{item_id}", json={"label": "Updated label"}, headers=auth_headers, ) assert patch_resp.status_code == 200 data = patch_resp.json() assert data["label"] == "Updated label" assert data["content"] == "Some content" # unchanged