"""Tests for src.gui.tools_registry — the per-tool manifest. The registry is loaded at import time by the home page sidebar nav, the home grid, and the findings panel's "Open Tool" links. A broken entry would surface as a sidebar disappearance, a missing card, or a ``KeyError`` in the findings rendering. We pin the invariants those call sites rely on: - Every page_slug points at a file that actually exists. - Every tool_id is unique (the analyzer keys findings on it). - Every section is one of the declared literals. - ``tool_by_id`` round-trips, ``display_name`` falls back gracefully. - ``section_label`` resolves localized labels. """ from __future__ import annotations from pathlib import Path from typing import get_args import pytest from src.gui.tools_registry import ( SECTION_LABELS, TOOLS, Section, Tier, Tool, display_name, section_label, tool_by_id, tool_description, tool_name, tools_for_tier, tools_in_section, ) PAGES_DIR = Path(__file__).resolve().parent.parent / "src" / "gui" / "pages" class TestRegistryInvariants: def test_all_tool_ids_are_unique(self): ids = [t.tool_id for t in TOOLS] assert len(ids) == len(set(ids)), ( f"duplicate tool_id in TOOLS: {sorted(ids)}" ) def test_all_page_slugs_point_at_real_files(self): for tool in TOOLS: page_file = PAGES_DIR / f"{tool.page_slug}.py" assert page_file.exists(), ( f"{tool.tool_id} → {tool.page_slug}.py does not exist" ) def test_all_sections_are_declared_literals(self): valid = set(get_args(Section)) for tool in TOOLS: assert tool.section in valid, ( f"{tool.tool_id} has unknown section {tool.section!r}; " f"valid: {sorted(valid)}" ) def test_all_tiers_are_declared_literals(self): valid = set(get_args(Tier)) for tool in TOOLS: assert tool.tier in valid, ( f"{tool.tool_id} has unknown tier {tool.tier!r}; " f"valid: {sorted(valid)}" ) def test_every_section_has_a_display_label(self): for section in get_args(Section): assert section in SECTION_LABELS, ( f"section {section!r} has no SECTION_LABELS entry" ) def test_no_orphan_section_labels(self): # The other direction: a SECTION_LABELS key that isn't a # declared Section literal is dead config. valid = set(get_args(Section)) for key in SECTION_LABELS: assert key in valid, ( f"SECTION_LABELS has stray key {key!r} not in Section" ) class TestToolLookups: def test_tool_by_id_round_trips_every_entry(self): for tool in TOOLS: found = tool_by_id(tool.tool_id) assert found is tool, ( f"tool_by_id({tool.tool_id!r}) returned {found!r}" ) def test_tool_by_id_returns_none_for_unknown(self): assert tool_by_id("not_a_real_tool_id") is None def test_display_name_falls_back_to_id(self): # Documented behavior: unknown id returns the id itself so the # bug is visible in the UI rather than crashing. assert display_name("not_a_real_tool_id") == "not_a_real_tool_id" def test_display_name_resolves_known_tool(self): # Pick a tool we know ships in every build. assert display_name("02_text_cleaner") == "Clean Text" class TestTierAndSectionFilters: def test_tools_for_tier_empty_returns_all(self): assert tools_for_tier() == list(TOOLS) def test_tools_for_tier_filters(self): # Every tool is tier="core" today, so an explicit core filter # should still match the full set. A "pro"-only call should # return an empty list. assert tools_for_tier("core") == list(TOOLS) assert tools_for_tier("pro") == [] def test_tools_in_section_preserves_registry_order(self): cleaners = tools_in_section("cleaners") in_full_order = [t for t in TOOLS if t.section == "cleaners"] assert cleaners == in_full_order @pytest.mark.parametrize("section", list(get_args(Section))) def test_every_section_has_at_least_one_tool(self, section): assert tools_in_section(section), ( f"section {section!r} has zero tools — sidebar group would be empty" ) class TestLocalizedAccessors: def test_tool_name_falls_back_to_registry_default(self): # An unknown tool id should return the literal id, not crash. assert tool_name("not_a_real_tool_id") == "not_a_real_tool_id" def test_tool_name_returns_localized_when_pack_has_key(self): # The lang packs ship a "tools.{id}.name" key for every shipped # tool. We don't assert the exact translation here (the lang # pack parity test pins that); we just check the helper returns # something non-empty and not the literal lookup key. name = tool_name("02_text_cleaner") assert name and name != "tools.02_text_cleaner.name" def test_tool_description_returns_localized_or_fallback(self): desc = tool_description("02_text_cleaner") assert desc and desc != "tools.02_text_cleaner.description" def test_tool_description_for_unknown_returns_empty(self): # Unknown ids return the registry fallback (""), not a # lookup-key string. The home grid avoids rendering empty # descriptions, so this contract matters. assert tool_description("not_a_real_tool_id") == "" @pytest.mark.parametrize("section", list(get_args(Section))) def test_section_label_returns_non_empty(self, section): label = section_label(section) assert label and label != f"nav.section_{section}" class TestReconcilerAndPdfArePresent: """The two newest pages were the most likely to be forgotten in the registry — pin them explicitly so a regression flagging "Ready" tools as missing from nav is loud.""" def test_pdf_extractor_present(self): tool = tool_by_id("10_pdf_extractor") assert tool is not None assert tool.page_slug == "10_PDF_Extractor" assert tool.status == "Ready" def test_reconciler_present(self): tool = tool_by_id("11_reconciler") assert tool is not None assert tool.page_slug == "11_Reconciler" assert tool.status == "Ready" # The new "analysis" section was introduced with this tool; # if the section disappears, the sidebar group goes empty. assert tool.section == "analysis"