The t59 F2 ruling, implemented. The ladder said ON was for "no source reported
anything at all" but tested for something looser — no transport data — so a
source that ANSWERED "not playing" still fell through to ON. A source that
answers is not a source that is silent, and `on` is the "we cannot know" state.
The ladder now tracks an explicit `reported` flag, set by every source that gave
us a power or playback fact, and the last tier is `IDLE if reported else ON`.
Each case the ruling names now lands on IDLE:
* a configured Jellyfin entity in ANY state — idle, off, standby, unavailable,
or even absent from the state machine. It reports "not playing" without an
early return, so a silent Jellyfin still cannot mask a live media session
below it (both directions pinned);
* a live media session that carries no playback flag — the app publishes a
session but is not playing, paused or buffering;
* a bare foreground app: either transport's answer counts (the protocol's
current_app push or the ADB mResumedActivity parse);
* a configured power entity, whatever it currently says;
* protocol is_on / ADB awake, plugged or not.
`on` survives only for an entry where none of those exists. The t55 rules are
untouched: an off power entity still outranks a playing session, and no source
may mask playing/paused/buffering.
The module docstring's tier 6, the `state` docstring and the README's ladder
(moving "no data" to the precise wording) all say the same thing now, and the
README's one-line state table was aligned with it.
RED PROOF (the new tests against the parent tree): five of the reporting cases
read `on` where they must read `idle` — `assert 'on' == 'idle'` — plus the
"ON is unreachable with any source configured" shape test.
SIMULATED vs EXECUTED: no real Shield was available, so these are the documented
source shapes, not captures; the state=6 → buffering mapping stays marked
inferred. No browser exists either, so the README pins are textual and the
production behaviour is exercised against real HA's state machine in
tests/test_ha_api_drift.py.
73 lines
2.6 KiB
Python
73 lines
2.6 KiB
Python
"""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
|