"""tests/test_readme.py — the README's user-facing contracts (t41). Documentation drifts silently: the seed example is read by people who then report "the key did nothing". These pins are deliberately textual — no YAML parser dependency — but they check the two things that actually hurt: * every key in the `nvidia_shield:` example is a key ``_yaml_seed_blocks`` honours (nothing documented that the integration ignores); * the replacement guide names the integrations and the glue it retires, so the t41 ruling stays visible to users, not only in the changelog. """ import re from pathlib import Path import pytest README = Path(__file__).resolve().parent.parent / "README.md" # helpers.seed_host / helpers.seed_adb_enabled read these; nothing else in a # seed block reaches the entry (the rest is the interactive flow's or the # options page's to set). SEEDED_KEYS = { "name", "host", "adb_port", "adb_enabled", "protocol_host", # legacy, still accepted "adb_host", # legacy, still accepted } @pytest.fixture(scope="module") def readme_text() -> str: return README.read_text(encoding="utf-8") def _seed_example(text: str) -> str: match = re.search(r"```yaml\n(nvidia_shield:\n(?:.*\n)*?)```", text) assert match, "the README has no `nvidia_shield:` example block" return match.group(1) def test_the_example_block_only_uses_seeded_keys(readme_text): keys = set(re.findall(r"^\s+([a-z_]+):", _seed_example(readme_text), re.M)) assert keys, "the example block declares no keys" assert keys <= SEEDED_KEYS, f"documented but ignored: {sorted(keys - SEEDED_KEYS)}" def test_the_example_block_documents_the_one_address_field(readme_text): example = _seed_example(readme_text) assert "host:" in example # the legacy spellings are still explained, as back-compat assert "protocol_host" in readme_text and "adb_host" in readme_text def test_the_replacement_guide_retires_the_old_stack(readme_text): for needle in ("androidtv_remote", "mediasession2mqtt", "universal", "Wholphin"): assert needle in readme_text, needle # and it says the UI is the primary path assert "primary path" in readme_text def test_the_readme_states_the_on_rule_precisely(readme_text): """t58 UNIT 0: "no data" was too loose; the README must not still say it.""" assert "no source reported a power or playback fact" in readme_text # the four reporting-but-not-playing shapes, named for shape in ("`idle`", "`standby`", "`unavailable`", "foreground app"): assert shape in readme_text, shape assert "no source reported anything at all" not in readme_text # ── t17: the intents documentation ── #: The keys a seeded intent row may carry (the seed validator in config_flow.py #: decides what it ACCEPTS; these are the names the README may use). INTENT_ROW_KEYS = { "id", "name", "package", "activity", "action", "categories", "data", "fields", } #: The keys one declared field may carry. INTENT_FIELD_KEYS = {"key", "type", "required", "default"} def _intents_example(text: str) -> str: """The `registered_intents:` block of the README's seed example (t17).""" match = re.search( r"```yaml\n(?:.*\n)*? registered_intents:\n((?:.*\n)*?)```", text ) assert match, "the README has no `registered_intents:` example block" return match.group(1) def test_the_intents_example_only_uses_keys_the_seed_honours(readme_text): """Documented-but-ignored is the failure this pins (t30's rule, nested).""" block = _intents_example(readme_text) row_keys = set(re.findall(r"^\s*([a-z_]+):", block, re.M)) field_keys = set(re.findall(r"[{,]\s*([a-z_]+):", block)) assert row_keys, "the intents example declares no row keys" assert row_keys <= INTENT_ROW_KEYS, sorted(row_keys - INTENT_ROW_KEYS) assert field_keys <= INTENT_ROW_KEYS | INTENT_FIELD_KEYS, sorted( field_keys - INTENT_ROW_KEYS - INTENT_FIELD_KEYS ) def test_the_intents_screen_and_action_are_documented(readme_text): """The screen, its fields, the button and the action are all named (t17).""" for needle in ( "### Intents (buttons and the `send_intent` action)", "Configure → **Intents**", "Variables (declared fields)", "One button per registered intent", "nvidia_shield.send_intent", "intent_response", ): assert needle in readme_text, needle def test_the_intents_copy_states_the_honest_failures(readme_text): """The README names each way this can go wrong — and that it RAISES (t17).""" for key in ( "unknown_intent", "missing_intent_field", "unknown_intent_variable", "invalid_intent_field", "intent_failed", "intents_need_adb", ): assert key in readme_text, key assert "configuration problem" in readme_text # the sentence wraps across lines in the source, so compare it flattened flat = " ".join(readme_text.split()) assert "no `eval`, no `format()`, no shell interpolation" in flat