F-A (HIGH): _user_schema_dict re-declared vol.Optional(CONF_ADB_HOST) /
CONF_PROTOCOL_HOST inside `if "host" in defaults:` — a no-op, because
voluptuous keys compare by field name and the first (defaultless) key object
survives. The discovered address therefore never reached the form, and the
user submitted a blank host. The host keys are now declared WITH default=host
(_host_field_key), so a discovery-driven user step arrives pre-filled.
F-A2: a discovery-driven user step says the address is known
("{discovery_note}Discovered Shield at <host> — its address is already filled
in below. ") through description_placeholders, and the no_state_source
re-render keeps the prefill instead of rebuilding a blank schema.
F-D: the ADB port is declared with default=DEFAULT_ADB_PORT (5555), and a YAML
seed's port hint is carried into the form instead of being flattened to 5555.
The seed host now feeds _seed_host (same prefill, no discovery claim).
Reproduce-through-test: tests/voluptuous_mirror.py mirrors the real
voluptuous.Marker key contract (hash/eq by field name, default=UNDEFINED when
omitted), because voluptuous is not installed in either CI variant and the old
bare-key stubs could not tell a schema WITH a default from one without. The
pre-t33 discovery assertion only checked the stored attribute and stayed green
while the form rendered blank; it now reads the schema keys and fails on the
pre-fix code (8 tests reproduced red locally).
Verified: uvx --with pytest-asyncio pytest tests -q → 242 passed, 1 skipped;
uvx ruff check custom_components tests → clean.
90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
"""A voluptuous mirror faithful enough to SEE a dropped schema default.
|
|
|
|
The test environment never installs voluptuous (the config-flow tests import
|
|
``config_flow`` against import-only stubs), so the harness has to model the two
|
|
behaviours the flow schemas depend on:
|
|
|
|
* ``Optional(field, default=value)`` / ``Required(field, default=value)`` carry
|
|
that default on the KEY object;
|
|
* keys compare by FIELD NAME only — real ``voluptuous.Marker`` implements
|
|
``__hash__ = hash(self.schema)`` and ``__eq__`` on class + schema. A later
|
|
re-declaration of the same field therefore keeps the FIRST key object, so a
|
|
default added by a second declaration is silently dropped.
|
|
|
|
That second property is exactly how the t33 F-A discovery-prefill bug hid from
|
|
the suite: the old stubs reduced every key to its bare string, so a schema
|
|
whose host key carried no default looked identical to one that carried the
|
|
discovered address. ``TestVoluptuousMirrorContract`` in
|
|
``tests/test_config_flow_ux.py`` pins both properties so the harness itself
|
|
cannot regress into blindness.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
UNDEFINED = object()
|
|
"""Sentinel for "this key declares no default" (real ``voluptuous.UNDEFINED``)."""
|
|
|
|
|
|
class Marker:
|
|
"""Stand-in for ``voluptuous.Marker`` (the ``Required``/``Optional`` base)."""
|
|
|
|
def __init__(self, schema, default=UNDEFINED):
|
|
self.schema = schema
|
|
self.default = default
|
|
|
|
def __hash__(self):
|
|
# real Marker hashes the field name only
|
|
return hash(self.schema)
|
|
|
|
def __eq__(self, other):
|
|
# real Marker compares class + field name, never the default
|
|
return type(other) is type(self) and other.schema == self.schema
|
|
|
|
def __repr__(self):
|
|
return f"<{type(self).__name__} {self.schema!r} default={self.default!r}>"
|
|
|
|
|
|
class Required(Marker):
|
|
"""Stand-in for ``voluptuous.Required``."""
|
|
|
|
|
|
class Optional(Marker):
|
|
"""Stand-in for ``voluptuous.Optional``."""
|
|
|
|
|
|
def install(vol) -> None:
|
|
"""Wire the mirror onto a stub ``voluptuous`` module object."""
|
|
vol.UNDEFINED = UNDEFINED
|
|
vol.Marker = Marker
|
|
vol.Required = Required
|
|
vol.Optional = Optional
|
|
vol.Schema = lambda schema: schema
|
|
|
|
|
|
def defaults_by_key(schema_dict) -> dict:
|
|
"""``{field: default}`` for a schema declared with mirror keys."""
|
|
return {key.schema: key.default for key in declared_keys(schema_dict)}
|
|
|
|
|
|
def declared_keys(schema_dict) -> list:
|
|
"""The schema's marker keys, in declaration order.
|
|
|
|
Raises on a non-marker key: a bare string key means the harness (or the
|
|
code under test) lost the KEY structure this mirror exists to check.
|
|
Test-harness bookkeeping keys (``__suggested__`` and friends) are skipped.
|
|
"""
|
|
keys = []
|
|
for key in schema_dict:
|
|
if isinstance(key, Marker):
|
|
keys.append(key)
|
|
continue
|
|
if isinstance(key, str) and key.startswith("__"):
|
|
continue
|
|
raise AssertionError(f"schema key {key!r} is not a voluptuous marker")
|
|
return keys
|
|
|
|
|
|
def declared_fields(schema_dict) -> list:
|
|
"""The field names of a schema, in declaration order."""
|
|
return [key.schema for key in declared_keys(schema_dict)]
|