diff --git a/backend/tests/test_sidebar_stats.py b/backend/tests/test_sidebar_stats.py new file mode 100644 index 00000000..dc5ec100 --- /dev/null +++ b/backend/tests/test_sidebar_stats.py @@ -0,0 +1,142 @@ +"""Integration tests for sidebar stats endpoint.""" + +import pytest +from httpx import AsyncClient + + +class TestSidebarStats: + """Tests for GET /sessions/sidebar-stats.""" + + @pytest.mark.asyncio + async def test_sidebar_stats_no_sessions( + self, client: AsyncClient, auth_headers: dict + ): + """Empty stats when user has no sessions.""" + response = await client.get( + "/api/v1/sessions/sidebar-stats?tz_offset=0", + headers=auth_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["resolved_today"] == 0 + assert data["active_count"] == 0 + assert data["total_session_minutes_today"] == 0 + assert data["active_sessions"] == [] + assert data["recent_completions"] == [] + assert "tree_counts" in data + + @pytest.mark.asyncio + async def test_sidebar_stats_with_active_session( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Active session appears in activity feed.""" + create_resp = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"], "ticket_number": "TK-100"}, + headers=auth_headers, + ) + assert create_resp.status_code == 201 + + response = await client.get( + "/api/v1/sessions/sidebar-stats?tz_offset=0", + headers=auth_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["active_count"] == 1 + assert len(data["active_sessions"]) == 1 + assert data["active_sessions"][0]["ticket_number"] == "TK-100" + assert data["active_sessions"][0]["tree_id"] == test_tree["id"] + + @pytest.mark.asyncio + async def test_sidebar_stats_resolved_today( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Resolved session counts in resolved_today.""" + create_resp = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers, + ) + session_id = create_resp.json()["id"] + + # Complete with resolved outcome + await client.post( + f"/api/v1/sessions/{session_id}/complete", + json={"outcome": "resolved", "outcome_notes": "Fixed it"}, + headers=auth_headers, + ) + + response = await client.get( + "/api/v1/sessions/sidebar-stats?tz_offset=0", + headers=auth_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["resolved_today"] >= 1 + assert data["active_count"] == 0 + assert len(data["recent_completions"]) >= 1 + + @pytest.mark.asyncio + async def test_sidebar_stats_max_active_sessions( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Active sessions capped at 5.""" + for i in range(7): + await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"], "ticket_number": f"TK-{i}"}, + headers=auth_headers, + ) + + response = await client.get( + "/api/v1/sessions/sidebar-stats?tz_offset=0", + headers=auth_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["active_count"] == 7 + assert len(data["active_sessions"]) == 5 + + @pytest.mark.asyncio + async def test_sidebar_stats_recent_completions_max_3( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Recent completions capped at 3.""" + for i in range(5): + create_resp = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers, + ) + session_id = create_resp.json()["id"] + await client.post( + f"/api/v1/sessions/{session_id}/complete", + json={"outcome": "resolved", "outcome_notes": "Done"}, + headers=auth_headers, + ) + + response = await client.get( + "/api/v1/sessions/sidebar-stats?tz_offset=0", + headers=auth_headers, + ) + assert response.status_code == 200 + data = response.json() + assert len(data["recent_completions"]) == 3 + + @pytest.mark.asyncio + async def test_sidebar_stats_requires_auth(self, client: AsyncClient): + """Endpoint requires authentication.""" + response = await client.get("/api/v1/sessions/sidebar-stats?tz_offset=0") + assert response.status_code == 401 + + @pytest.mark.asyncio + async def test_sidebar_stats_requires_tz_offset( + self, client: AsyncClient, auth_headers: dict + ): + """tz_offset query param is required.""" + response = await client.get( + "/api/v1/sessions/sidebar-stats", + headers=auth_headers, + ) + assert response.status_code == 422