fix(station): Signiervariante aus Produkt-ID und Version bestimmen
Eine Entwicklungs-G95 meldete sich am 2026-09-15 mit einer Produkt-ID OHNE
"-dev", aber einer Version MIT "-dev". products.py leitete die Variante
ausschliesslich aus der Produkt-ID ab, die Lampe galt als Produktionsgeraet,
bekam das produktionssigniertes Image und lehnte es ab - ihr Bootloader prueft
gegen den Entwicklungsschluessel.
Die Variante ist damit keine Eigenschaft der Produkt-ID, sondern der Anfrage.
PRODUCTS bildet nur noch auf Geraetetyp und Anzeigename ab; beide Schreibweisen
der Produkt-ID fuehren auf denselben Eintrag. classify_variant() in
parse_request() wertet Produkt-ID UND Version aus: "-dev" an einer der beiden
Stellen bedeutet Entwicklungsgeraet, in der Version nur als ganzes Wort
("-devel"/"-dev2" zaehlen nicht).
Damit eine Fehlklassifikation nicht wieder erst an der Lampe auffaellt, fuehren
status.json (last_seen und jeder recent-Eintrag) und das Panel die Quelle der
Einstufung mit: "g95_cw (dev/Ver)", "dev/ID", "dev/ID+Ver" oder "prod".
Tests: die drei am Geraet mitgeschnittenen Anfragen plus die vierte denkbare
Kombination, die Wortgrenze in der Version, zwei Ende-zu-Ende-Tests im Server
(dev-Image statt prod-Image) und die Panel-Beschriftung.
This commit is contained in:
@@ -7,43 +7,98 @@ import products # noqa: E402
|
||||
|
||||
|
||||
def test_table_is_complete():
|
||||
"""Acht Geraete mal zwei Varianten, minus das EVK, das keine eigene Dev-ID hat."""
|
||||
"""Sieben Geraete in beiden Schreibweisen der Produkt-ID, plus das EVK, das nur eine
|
||||
Produktions-ID hat."""
|
||||
assert len(products.PRODUCTS) == 15
|
||||
targets = {p.target for p in products.PRODUCTS.values()}
|
||||
assert targets == {"evk", "e27_rgb", "e14_rgb", "gu10_rgb",
|
||||
"e27_cw", "e14_cw", "gu10_cw", "g95_cw"}
|
||||
|
||||
|
||||
def test_production_id_maps_to_prod_variant():
|
||||
def test_table_carries_no_variant():
|
||||
"""Die Variante ist eine Eigenschaft der Anfrage, nicht der Produkt-ID - sie darf gar
|
||||
nicht mehr aus der Tabelle zu holen sein (Hardwarebefund 2026-09-15)."""
|
||||
assert not hasattr(products.PRODUCTS["-klyqa.lighting.cw-ww.g95"], "variant")
|
||||
assert "variant" not in products.Product._fields
|
||||
|
||||
|
||||
def test_both_spellings_map_to_the_same_device():
|
||||
with_dev = products.PRODUCTS["-klyqa.lighting.cw-ww.g95-dev"]
|
||||
without = products.PRODUCTS["-klyqa.lighting.cw-ww.g95"]
|
||||
assert with_dev.target == without.target == "g95_cw"
|
||||
assert with_dev.name == without.name == "Klyqa G95 Vintage CW/WW"
|
||||
|
||||
|
||||
def test_production_id_maps_to_device_and_name():
|
||||
p = products.PRODUCTS["-klyqa.lighting.rgb-cw-ww.gu10"]
|
||||
assert (p.target, p.variant) == ("gu10_rgb", "prod")
|
||||
assert p.target == "gu10_rgb"
|
||||
assert p.name == "Klyqa GU10 RGB CW/WW"
|
||||
|
||||
|
||||
def test_dev_suffix_selects_dev_variant():
|
||||
p = products.PRODUCTS["-klyqa.lighting.cw-ww.g95-dev"]
|
||||
assert (p.target, p.variant) == ("g95_cw", "dev")
|
||||
|
||||
|
||||
def test_parses_real_request_from_g95():
|
||||
"""Am 2026-09-13 an einer echten G95 protokolliert."""
|
||||
def test_parses_real_request_from_g95_dev_in_both_places():
|
||||
"""Am 2026-09-13 an einer echten Entwicklungs-G95 protokolliert: "-dev" in Produkt-ID
|
||||
UND Version."""
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.cw-ww.g95-dev_V1.1.1.1800-dev_ota_light.img")
|
||||
assert r is not None
|
||||
assert r.product_id == "-klyqa.lighting.cw-ww.g95-dev"
|
||||
assert r.lamp_version == "V1.1.1.1800-dev"
|
||||
assert r.product.target == "g95_cw"
|
||||
assert r.product.variant == "dev"
|
||||
assert (r.variant, r.variant_source) == ("dev", "both")
|
||||
|
||||
|
||||
def test_parses_real_request_from_gu10():
|
||||
def test_parses_real_request_from_g95_dev_only_in_version():
|
||||
"""Am 2026-09-15 an einer echten Entwicklungs-G95 protokolliert - die Anfrage, an der
|
||||
die Station bisher scheiterte: Produkt-ID OHNE "-dev", Version MIT. Sie muss trotzdem
|
||||
als Entwicklungsgeraet gelten, sonst geht das produktionssignierte Image raus."""
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.cw-ww.g95_V1.1.1.2357-dev_ota_light.img")
|
||||
assert r is not None
|
||||
assert r.product_id == "-klyqa.lighting.cw-ww.g95"
|
||||
assert r.lamp_version == "V1.1.1.2357-dev"
|
||||
assert r.product.target == "g95_cw"
|
||||
assert (r.variant, r.variant_source) == ("dev", "version")
|
||||
|
||||
|
||||
def test_parses_real_request_from_gu10_without_any_dev():
|
||||
"""Die erfolgreich migrierte Produktionslampe: nirgends ein "-dev"."""
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.rgb-cw-ww.gu10_V1.1.0.1138_ota_light.img")
|
||||
assert r.product.target == "gu10_rgb"
|
||||
assert r.product.variant == "prod"
|
||||
assert (r.variant, r.variant_source) == ("prod", "none")
|
||||
assert r.lamp_version == "V1.1.0.1138"
|
||||
|
||||
|
||||
def test_dev_only_in_product_id_is_dev():
|
||||
"""Die vierte denkbare Kombination - so noch nicht am Geraet gesehen, aber die
|
||||
Machbarkeitsprobe zeigt, dass die Produkt-ID das "-dev" tragen kann, und nichts
|
||||
garantiert, dass die Version es dann auch tut."""
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.cw-ww.g95-dev_V1.1.1.1800_ota_light.img")
|
||||
assert r.product.target == "g95_cw"
|
||||
assert (r.variant, r.variant_source) == ("dev", "product_id")
|
||||
|
||||
|
||||
def test_dev_must_be_a_whole_word_in_the_version():
|
||||
""""-devel"/"-dev2" sind kein Entwicklungskennzeichen - sonst wuerde eine
|
||||
Produktionslampe mit so einer Version das dev-signierte Image bekommen."""
|
||||
for version in ("V1.1.1.2357-devel", "V1.1.1.2357-dev2"):
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.cw-ww.g95_" + version + "_ota_light.img")
|
||||
assert (r.variant, r.variant_source) == ("prod", "none"), version
|
||||
# ... ein "-dev" vor einem Trennzeichen dagegen schon.
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-klyqa.lighting.cw-ww.g95_V1.1-dev.3_ota_light.img")
|
||||
assert (r.variant, r.variant_source) == ("dev", "version")
|
||||
|
||||
|
||||
def test_variant_is_classified_even_for_unknown_product_ids():
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-acme.lightbulb.xxl_V9.9-dev_ota_light.img")
|
||||
assert r.product is None
|
||||
assert (r.variant, r.variant_source) == ("dev", "version")
|
||||
|
||||
|
||||
def test_unknown_product_id_parses_but_has_no_product():
|
||||
r = products.parse_request(
|
||||
"/qcx_firmware_-acme.lightbulb.xxl_V9.9_ota_light.img")
|
||||
|
||||
Reference in New Issue
Block a user