"""get_knowledge_counts — one number per facet, and an honest total. Two grouped queries, one per typing axis. It used to be three: a grouped count over note_type restricted to ("note", "process"), a scalar count of tasks, and a second scalar just for plans. That shape is why `issue` had no number — every kind needed a query of its own and nobody added one — and why the "All" chip sat ~90 below the list it labelled, since snippets were in the feed but in no count (#3128). """ from unittest.mock import AsyncMock, MagicMock, patch import pytest from tests.helpers import make_mock_session def _grouped(rows): r = MagicMock() r.all.return_value = rows return r async def _counts(non_task_rows, kind_rows, **kwargs): session = make_mock_session() # 1) non-task rows grouped by note_type, 2) task rows grouped by task_kind session.execute = AsyncMock( side_effect=[_grouped(non_task_rows), _grouped(kind_rows)] ) with patch("scribe.services.knowledge.async_session") as cls: cls.return_value = session from scribe.services.knowledge import get_knowledge_counts return await get_knowledge_counts(user_id=1, **kwargs), session @pytest.mark.asyncio async def test_every_facet_gets_a_number_including_the_kinds(): counts, _ = await _counts( [("note", 395), ("process", 3), ("snippet", 90)], [("work", 2104), ("issue", 435), ("spike", 1), ("plan", 90)], ) assert counts["note"] == 395 assert counts["process"] == 3 assert counts["snippet"] == 90 assert counts["issue"] == 435 assert counts["spike"] == 1 assert counts["plan"] == 90 assert counts["work"] == 2104 @pytest.mark.asyncio async def test_task_is_the_sum_of_its_kinds(): """`task` is not counted separately any more — it is what the kinds add up to, so the two can't disagree.""" counts, _ = await _counts([], [("work", 2104), ("issue", 435), ("spike", 1)]) assert counts["task"] == 2540 @pytest.mark.asyncio async def test_total_counts_snippets_and_counts_no_task_twice(): """The All chip labels a feed that contains every kind, so it has to count every kind — and exactly once. Kinds are subsets of `task`; adding them would count each issue a second time.""" counts, _ = await _counts( [("note", 10), ("process", 2), ("snippet", 5)], [("work", 20), ("issue", 4)], ) assert counts["task"] == 24 assert counts["total"] == 10 + 2 + 5 + 24 @pytest.mark.asyncio async def test_absent_facets_report_zero_rather_than_missing(): counts, _ = await _counts([("note", 1)], []) assert counts["note"] == 1 for key in ("process", "snippet", "task", "work", "issue", "spike", "plan"): assert counts[key] == 0, key assert counts["total"] == 1 @pytest.mark.asyncio async def test_a_tag_filter_narrows_both_axes(): """A tag has to reach both queries, or the chips would disagree with each other under a filter — tasks narrowed, notes not.""" _, session = await _counts([], [], tags=["python"]) assert session.execute.await_count == 2 for call in session.execute.await_args_list: assert "notes.tags" in str(call.args[0])