"""Ticket #17 prefactor inventory: central identity + rename surfaces. Seams under test (per spec Testing Decisions): - Seam 1 (Python behaviour suite): central constants hold current values and every previously duplicated consumer resolves through the shared definition. - Seam 2 (offline policy test): rename/retained inventories enumerate files the rename ticket must flip (or deliberately keep) without network access. No user-visible behaviour, name, path, or string changes: all asserted values are the pre-rename Vocalinux/vocalinux literals. """ import ast from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] SRC_ROOT = REPO_ROOT / "src" / "vocalinux" def _load_identity(): from vocalinux import app_identity return app_identity def test_central_values_are_current_names(): """Shared definition still carries the pre-rename literals.""" identity = _load_identity() assert identity.APP_NAME == "Vocalinux" assert identity.APP_ID == "vocalinux" assert identity.APP_DIR_NAME == "vocalinux" assert identity.CLI_PROG == "vocalinux" assert identity.BUS_NAME == "com.vocalinux.Vocalinux" assert identity.OBJECT_PATH == "/com/vocalinux/Vocalinux" assert identity.INTERFACE_NAME == "com.vocalinux.Vocalinux" assert identity.DESKTOP_FILENAME == "vocalinux.desktop" assert identity.DESKTOP_ICON_NAME == "vocalinux" assert identity.ENGINE_NAME == "vocalinux" assert identity.ENGINE_LONGNAME == "Vocalinux" assert identity.COMPONENT_NAME == "org.freedesktop.IBus.Vocalinux" assert identity.IBUS_DIR_NAME == "vocalinux-ibus" assert identity.UPDATE_CHECKER_USER_AGENT == "Vocalinux-UpdateChecker" assert identity.RESOURCE_DIR_NAME == "vocalinux" assert identity.VERIFICATION_STAMP_NAME == ".vocalinux_verified" assert identity.LOG_FILE_PREFIX == "vocalinux_logs_" assert identity.PORTAL_TOKEN_PREFIX == "vocalinux_" assert identity.ENV_FORCE_BACKEND == "VOCALINUX_FORCE_BACKEND" assert identity.ENV_INJECT_MODIFIER_WAIT == "VOCALINUX_INJECT_MODIFIER_WAIT" assert identity.ENV_YDOTOOL_KEY_DELAY == "VOCALINUX_YDOTOOL_KEY_DELAY" def _imports_app_identity(path: Path) -> bool: tree = ast.parse(path.read_text(encoding="utf-8")) for node in ast.walk(tree): if isinstance(node, ast.ImportFrom): if node.module and node.module.endswith("app_identity"): return True return False def test_consumers_resolve_through_shared_definition(): """Previously duplicated surfaces import the shared definition (AST, offline).""" consumers = [ SRC_ROOT / "utils" / "paths.py", SRC_ROOT / "ui" / "notifications.py", SRC_ROOT / "ui" / "tray_indicator.py", SRC_ROOT / "dbus_service.py", SRC_ROOT / "ui" / "autostart_manager.py", SRC_ROOT / "text_injection" / "ibus_engine.py", SRC_ROOT / "utils" / "update_checker.py", SRC_ROOT / "utils" / "resource_manager.py", SRC_ROOT / "main.py", SRC_ROOT / "text_injection" / "text_injector.py", SRC_ROOT / "text_injection" / "remote_desktop_portal.py", SRC_ROOT / "ui" / "logging_dialog.py", SRC_ROOT / "utils" / "model_checksums.py", SRC_ROOT / "speech_recognition" / "recognition_manager.py", SRC_ROOT / "ui" / "settings_dialog.py", SRC_ROOT / "ui" / "dictation_overlay.py", SRC_ROOT / "ui" / "audio_feedback.py", SRC_ROOT / "ui" / "keyboard_backends" / "__init__.py", ] missing = [str(p) for p in consumers if not p.exists()] assert not missing, f"consumer files missing: {missing}" not_centralised = [str(p) for p in consumers if not _imports_app_identity(p)] assert not_centralised == [], f"consumers not resolving through app_identity: {not_centralised}" def test_lightweight_consumers_match_shared_values_at_runtime(): """Runtime check where importing the consumer needs no display server.""" identity = _load_identity() from vocalinux.ui import autostart_manager, notifications from vocalinux.utils import paths assert paths.APP_DIR_NAME == identity.APP_DIR_NAME assert autostart_manager.DESKTOP_FILENAME == identity.DESKTOP_FILENAME assert notifications.APP_NAME == identity.APP_NAME def test_source_pinned_literals_reserved_for_rename(): """Literals existing source-shape tests pin stay literal until the rename. ``tests/test_settings_dialog.py`` (About page) and ``tests/test_dictation_pad.py`` (shelving) read module source or exec function bodies in isolation, so ``settings_about.py`` and ``dictation_pad.py`` keep their current literals. They are recorded here as checked rename surfaces for ticket #18 rather than centralised twice. """ about = (SRC_ROOT / "ui" / "settings_about.py").read_text(encoding="utf-8") for literal in ( 'VOCALINUX_SITE_URL = "https://vocalinux.com"', 'title="Vocalinux"', 'get_icon_path("vocalinux")', ): assert literal in about, literal pad = (SRC_ROOT / "ui" / "dictation_pad.py").read_text(encoding="utf-8") assert 'window.set_title("Vocalinux Dictation Pad")' in pad def test_rename_inventory_files_exist(): """Every rename surface the rename ticket must flip is a real checked file.""" identity = _load_identity() assert identity.RENAME_SURFACES, "rename inventory must not be empty" missing = [rel for rel in identity.RENAME_SURFACES if not (REPO_ROOT / rel).exists()] assert missing == [], f"rename inventory entries missing on disk: {missing}" def test_retained_inventory_records_internal_names(): """Internal module name and upstream URLs stay, by record not by accident.""" identity = _load_identity() assert any("src/vocalinux" in entry for entry in identity.RETAINED_SURFACES) assert any("github.com/VocaHQ/vocalinux" in entry for entry in identity.RETAINED_SURFACES) # The retained Python package must still import under its current name. import vocalinux assert vocalinux.__name__ == "vocalinux"