From d85d22661bcf06227682ef9189475cad2c28c3ff Mon Sep 17 00:00:00 2001 From: leetcrypt Date: Tue, 21 Jul 2026 12:08:05 -0700 Subject: [PATCH] test: cover decoded KEY exact signature-DB matching 10 offline tests for the KEY-file identification path: a named protocol that is in the DB resolves to the real catalog signature (category + manufacturer) via match_method 'decoded_key_exact' with frequency-aware confidence (0.95 consistent / 0.90 off-band); unknown names fall back to the router ('decoded_key', <0.90) yet stay identified; plus _lookup_known_protocol case-insensitivity and None handling. Values are drawn from the DB itself so the suite tracks catalog changes. Bare pytest, no server. Co-Authored-By: Claude Opus 4.6 --- tests/unit/test_decoded_key_identification.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/unit/test_decoded_key_identification.py diff --git a/tests/unit/test_decoded_key_identification.py b/tests/unit/test_decoded_key_identification.py new file mode 100644 index 0000000..0523ffe --- /dev/null +++ b/tests/unit/test_decoded_key_identification.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +Offline unit tests for decoded (KEY) .sub identification against the signature DB. + +A Flipper KEY file carries no RAW pulses but *names* a decoded protocol (e.g. +``Protocol: Princeton``). When that name is in our signature database this is the +strongest possible identification -- the capture device already demodulated and +named the protocol and we recognise it -- so it must: + + - resolve to the REAL catalog signature (true manufacturer + category), not the + category router's coarser guess (Princeton is a Garage Door Opener in the + catalog, which the router previously mislabelled "Remote Control"), and + - score with high, frequency-aware confidence (exact database match), not the + ~0.45 fuzzy-guess confidence it used to get. + +Unknown protocol names must still fall back to category routing so an upload is +identified by family rather than silently dropped ("accept any capture -> +identify the device"). + +Offline / author-time: bare ``pytest`` runs this with no docker, no server, no +network. KEY .sub content is embedded and written to ``tmp_path``. +""" + +import pytest + +from src.parser.sub_parser import SubFileParser +from src.matcher.engine import SignatureMatcher +from src.matcher.pattern_decoder import PatternDecoder +from src.matcher.protocol_database import get_protocol_database + + +def _key_sub(protocol: str, frequency: int, bit: int = 24, key: str = "00 00 00 00 00 95 D5 D4") -> str: + return "\n".join([ + "Filetype: Flipper SubGhz Key File", + "Version: 1", + f"Frequency: {frequency}", + "Preset: FuriHalSubGhzPresetOok270Async", + f"Protocol: {protocol}", + f"Bit: {bit}", + f"Key: {key}", + "TE: 400", + "", + ]) + + +def _write(tmp_path, content, name="k.sub"): + p = tmp_path / name + p.write_text(content) + return str(p) + + +def _identify(tmp_path, content): + meta = SubFileParser().parse(_write(tmp_path, content)) + return meta, SignatureMatcher().match(meta, max_results=5) + + +# A known catalog protocol and a known one that carries a manufacturer, both +# taken from the DB itself so the tests stay correct if the catalog changes. +_DB = get_protocol_database() +_KNOWN = next(p for p in _DB.get_all()) +_KNOWN_WITH_MFR = next((p for p in _DB.get_all() if p.manufacturer), None) +_UNKNOWN_NAME = "ZZ_NOT_A_REAL_PROTOCOL_9999" + + +class TestKnownProtocolExactMatch: + def test_named_protocol_resolves_to_catalog_signature(self, tmp_path): + meta, matches = _identify(tmp_path, _key_sub(_KNOWN.name, _KNOWN.frequency)) + assert meta.file_format == "KEY" + assert meta.is_decoded is True + assert len(matches) >= 1 + top = matches[0] + assert top.device_name == _KNOWN.name + assert top.match_method == "decoded_key_exact" + # Category comes from the catalog, not the router's guess. + assert top.match_details.get("device_category") == _KNOWN.category + + def test_exact_match_scores_high_when_frequency_consistent(self, tmp_path): + _, matches = _identify(tmp_path, _key_sub(_KNOWN.name, _KNOWN.frequency)) + assert matches[0].confidence == pytest.approx(0.95) + assert matches[0].match_details.get("frequency_match") is True + + def test_exact_match_still_high_but_lower_on_frequency_mismatch(self, tmp_path): + # Same known name, but a frequency far outside its band. + off_band = _KNOWN.frequency + 50_000_000 + _, matches = _identify(tmp_path, _key_sub(_KNOWN.name, off_band)) + top = matches[0] + assert top.match_method == "decoded_key_exact" + assert top.confidence == pytest.approx(0.90) + assert top.match_details.get("frequency_match") is False + + @pytest.mark.skipif(_KNOWN_WITH_MFR is None, reason="no catalog protocol carries a manufacturer") + def test_real_manufacturer_is_surfaced(self, tmp_path): + _, matches = _identify( + tmp_path, _key_sub(_KNOWN_WITH_MFR.name, _KNOWN_WITH_MFR.frequency, bit=66) + ) + assert matches[0].manufacturer == _KNOWN_WITH_MFR.manufacturer + + +class TestUnknownProtocolFallback: + def test_unknown_name_falls_back_to_router(self, tmp_path): + _, matches = _identify(tmp_path, _key_sub(_UNKNOWN_NAME, 433920000)) + assert len(matches) >= 1, "unknown decoded protocol must still be identified by family" + top = matches[0] + assert top.match_method == "decoded_key" + # A routed guess must not masquerade as an exact database match. + assert top.confidence < 0.90 + + def test_unknown_name_still_names_the_protocol(self, tmp_path): + _, matches = _identify(tmp_path, _key_sub(_UNKNOWN_NAME, 433920000)) + assert matches[0].device_name == _UNKNOWN_NAME + + +class TestLookupHelper: + def test_known_name_case_insensitive(self): + dec = PatternDecoder() + sig = dec._lookup_known_protocol(_KNOWN.name.upper(), _KNOWN.frequency) + assert sig is not None + assert sig.name == _KNOWN.name + + def test_unknown_name_returns_none(self): + assert PatternDecoder()._lookup_known_protocol(_UNKNOWN_NAME, 433920000) is None + + def test_empty_name_returns_none(self): + assert PatternDecoder()._lookup_known_protocol(None, 433920000) is None + + def test_prefers_frequency_matching_candidate(self): + """If duplicate-named entries exist, the frequency-consistent one wins.""" + dec = PatternDecoder() + sig = dec._lookup_known_protocol(_KNOWN.name, _KNOWN.frequency) + assert sig is not None + assert sig.matches_frequency(_KNOWN.frequency)