Files
ninlight-w600/tools/station/tests/test_products.py
T
Robin von Bülow 09ee206906 feat(station): Montana-Black-Produkt-ID -> Ziel ledshield_rgb
Meldet eine Lampe @qcx.montana.black.ledshield.rgb(-dev), sucht die Station
jetzt ledshield_rgb_(dev|prod).img statt zufaellig das E27-Image.
Produktname/Ziel aus qcl_product_info.c der Original-Firmware.
2026-09-25 17:39:41 +02:00

126 lines
5.2 KiB
Python

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import products # noqa: E402
def test_table_is_complete():
"""Acht Geraete in beiden Schreibweisen der Produkt-ID, plus das EVK, das nur eine
Produktions-ID hat."""
assert len(products.PRODUCTS) == 17
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", "ledshield_rgb"}
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 == "gu10_rgb"
assert p.name == "Klyqa GU10 RGB CW/WW"
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.variant, r.variant_source) == ("dev", "both")
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.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")
assert r is not None
assert r.product is None
assert r.product_id == "-acme.lightbulb.xxl"
def test_non_matching_paths():
for p in ("/", "/ninlight_evk_ota.img", "/qcx_firmware_.img",
"/qcx_firmware_-klyqa.lighting.cw-ww.g95_ota_light.img",
"/x/qcx_firmware_-klyqa.lighting.cw-ww.g95_V1_ota_light.img"):
assert products.parse_request(p) is None, p
def test_path_traversal_is_not_parseable():
assert products.parse_request(
"/qcx_firmware_../../etc/passwd_V1_ota_light.img") is None
def test_websafe_transform():
assert products.websafe("@klyqa.lighting.cw-ww.g95") == "-klyqa.lighting.cw-ww.g95"
assert products.websafe("a b/c") == "a-b-c"
assert products.websafe("keep+all-_.09Az") == "keep+all-_.09Az"