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"