"""Unit tests for app.analytics.queries.""" from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest pytestmark = pytest.mark.unit def _make_pool_with_fetchone(result: dict | None) -> MagicMock: mock_cursor = AsyncMock() mock_cursor.fetchone = AsyncMock(return_value=result) mock_conn = AsyncMock() mock_conn.execute = AsyncMock(return_value=mock_cursor) mock_ctx = AsyncMock() mock_ctx.__aenter__ = AsyncMock(return_value=mock_conn) mock_ctx.__aexit__ = AsyncMock(return_value=None) mock_pool = MagicMock() mock_pool.connection.return_value = mock_ctx return mock_pool def _make_pool_with_fetchall(result: list[dict]) -> MagicMock: mock_cursor = AsyncMock() mock_cursor.fetchall = AsyncMock(return_value=result) mock_conn = AsyncMock() mock_conn.execute = AsyncMock(return_value=mock_cursor) mock_ctx = AsyncMock() mock_ctx.__aenter__ = AsyncMock(return_value=mock_conn) mock_ctx.__aexit__ = AsyncMock(return_value=None) mock_pool = MagicMock() mock_pool.connection.return_value = mock_ctx return mock_pool class TestResolutionRate: @pytest.mark.asyncio async def test_returns_float(self) -> None: from app.analytics.queries import resolution_rate pool = _make_pool_with_fetchone({"rate": 0.85}) result = await resolution_rate(pool, range_days=7) assert isinstance(result, float) @pytest.mark.asyncio async def test_zero_state_returns_zero(self) -> None: from app.analytics.queries import resolution_rate pool = _make_pool_with_fetchone(None) result = await resolution_rate(pool, range_days=7) assert result == 0.0 @pytest.mark.asyncio async def test_returns_correct_value(self) -> None: from app.analytics.queries import resolution_rate pool = _make_pool_with_fetchone({"rate": 0.75}) result = await resolution_rate(pool, range_days=7) assert result == 0.75 class TestAgentUsageQuery: @pytest.mark.asyncio async def test_returns_tuple(self) -> None: from app.analytics.queries import agent_usage pool = _make_pool_with_fetchall([]) result = await agent_usage(pool, range_days=7) assert isinstance(result, tuple) @pytest.mark.asyncio async def test_empty_state_returns_empty_tuple(self) -> None: from app.analytics.queries import agent_usage pool = _make_pool_with_fetchall([]) result = await agent_usage(pool, range_days=7) assert result == () @pytest.mark.asyncio async def test_maps_rows_to_agent_usage_objects(self) -> None: from app.analytics.models import AgentUsage from app.analytics.queries import agent_usage pool = _make_pool_with_fetchall([ {"agent": "order_agent", "count": 10, "percentage": 66.7}, {"agent": "discount_agent", "count": 5, "percentage": 33.3}, ]) result = await agent_usage(pool, range_days=7) assert len(result) == 2 assert isinstance(result[0], AgentUsage) assert result[0].agent == "order_agent" assert result[0].count == 10 class TestEscalationRate: @pytest.mark.asyncio async def test_returns_float(self) -> None: from app.analytics.queries import escalation_rate pool = _make_pool_with_fetchone({"rate": 0.05}) result = await escalation_rate(pool, range_days=7) assert isinstance(result, float) @pytest.mark.asyncio async def test_zero_state_returns_zero(self) -> None: from app.analytics.queries import escalation_rate pool = _make_pool_with_fetchone(None) result = await escalation_rate(pool, range_days=7) assert result == 0.0 class TestCostPerConversation: @pytest.mark.asyncio async def test_returns_float(self) -> None: from app.analytics.queries import cost_per_conversation pool = _make_pool_with_fetchone({"avg_cost": 0.03}) result = await cost_per_conversation(pool, range_days=7) assert isinstance(result, float) @pytest.mark.asyncio async def test_zero_state_returns_zero(self) -> None: from app.analytics.queries import cost_per_conversation pool = _make_pool_with_fetchone(None) result = await cost_per_conversation(pool, range_days=7) assert result == 0.0 class TestInterruptStatsQuery: @pytest.mark.asyncio async def test_returns_interrupt_stats(self) -> None: from app.analytics.models import InterruptStats from app.analytics.queries import interrupt_stats pool = _make_pool_with_fetchone( {"total": 10, "approved": 7, "rejected": 2, "expired": 1} ) result = await interrupt_stats(pool, range_days=7) assert isinstance(result, InterruptStats) assert result.total == 10 assert result.approved == 7 @pytest.mark.asyncio async def test_zero_state_returns_zeros(self) -> None: from app.analytics.models import InterruptStats from app.analytics.queries import interrupt_stats pool = _make_pool_with_fetchone(None) result = await interrupt_stats(pool, range_days=7) assert isinstance(result, InterruptStats) assert result.total == 0 assert result.approved == 0 assert result.rejected == 0 assert result.expired == 0 class TestGetAnalytics: @pytest.mark.asyncio async def test_returns_analytics_result(self) -> None: from unittest.mock import patch from app.analytics.models import AnalyticsResult, InterruptStats from app.analytics.queries import get_analytics mock_pool = MagicMock() with ( patch("app.analytics.queries.resolution_rate", return_value=0.85), patch("app.analytics.queries.escalation_rate", return_value=0.05), patch("app.analytics.queries.cost_per_conversation", return_value=0.03), patch("app.analytics.queries.agent_usage", return_value=()), patch( "app.analytics.queries.interrupt_stats", return_value=InterruptStats(), ), patch("app.analytics.queries._total_conversations", return_value=100), patch("app.analytics.queries._avg_turns", return_value=4.2), ): result = await get_analytics(mock_pool, range_days=7) assert isinstance(result, AnalyticsResult) assert result.range == "7d" assert result.total_conversations == 100 assert result.resolution_rate == 0.85 @pytest.mark.asyncio async def test_zero_state_returns_zeros(self) -> None: from unittest.mock import patch from app.analytics.models import AnalyticsResult, InterruptStats from app.analytics.queries import get_analytics mock_pool = MagicMock() with ( patch("app.analytics.queries.resolution_rate", return_value=0.0), patch("app.analytics.queries.escalation_rate", return_value=0.0), patch("app.analytics.queries.cost_per_conversation", return_value=0.0), patch("app.analytics.queries.agent_usage", return_value=()), patch("app.analytics.queries.interrupt_stats", return_value=InterruptStats()), patch("app.analytics.queries._total_conversations", return_value=0), patch("app.analytics.queries._avg_turns", return_value=0.0), ): result = await get_analytics(mock_pool, range_days=7) assert isinstance(result, AnalyticsResult) assert result.total_conversations == 0 assert result.resolution_rate == 0.0 assert result.agent_usage == ()