| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- """The HMS catalogue generated from Bambu Studio's HMS files (#2728)."""
- import json
- import re
- import pytest
- from backend.app.services import hms_errors
- from backend.app.services.hms_errors import (
- alert_level_from_print_error,
- describe_fault,
- get_error_description,
- lookup_fault,
- )
- CATALOG = json.loads(hms_errors._DATA_FILE.read_text(encoding="utf-8"))
- EXTRA = json.loads((hms_errors._DATA_FILE.parent / "hms_catalog_extra.json").read_text(encoding="utf-8"))
- class TestCatalogFile:
- """The file is generated by scripts/generate_hms_catalog.py and must stay in
- the shape that script writes; these fail on a hand edit that breaks it."""
- def test_records_where_it_came_from(self):
- """So "how old is our table" has an answer, and a regeneration shows up
- in the diff as a new commit and new file versions."""
- source = CATALOG["_source"]
- assert source["generator"] == "scripts/generate_hms_catalog.py"
- assert re.fullmatch(r"[0-9a-f]{7,40}|unknown", source["bambu_studio_commit"])
- assert source["model_file_versions"]
- assert all(isinstance(v, int) for v in source["model_file_versions"].values())
- @pytest.mark.parametrize(("section", "length"), [("hms", 16), ("error", 8)])
- def test_keys_are_uppercase_hex_of_the_right_length(self, section, length):
- pattern = re.compile(rf"^[0-9A-F]{{{length}}}$")
- assert CATALOG[section]
- for code in CATALOG[section]:
- assert pattern.match(code), code
- for model, table in CATALOG[f"{section}_by_model"].items():
- assert re.fullmatch(r"[0-9A-Z]{3}", model)
- for code in table:
- assert pattern.match(code), (model, code)
- @pytest.mark.parametrize("section", ["hms", "error"])
- def test_a_model_override_differs_from_the_merged_text(self, section):
- """Overrides hold only what differs; an identical one is dead weight and
- means the file was not written by the script."""
- for model, table in CATALOG[f"{section}_by_model"].items():
- for code, text in table.items():
- assert CATALOG[section].get(code) != text, (model, code)
- def test_every_extra_code_is_in_the_catalogue(self):
- """The hand-kept extras are merged in where Studio has no entry, so a
- regeneration cannot drop the texts Bambuddy already had."""
- for code in EXTRA["error"]:
- assert code in CATALOG["error"], code
- def test_texts_are_strings(self):
- for section in ("hms", "error"):
- assert all(isinstance(t, str) for t in CATALOG[section].values())
- class TestLookup:
- def test_resolves_a_print_error_code(self):
- assert describe_fault("03008004") == "Filament ran out. Please load new filament."
- def test_resolves_an_hms_code_by_its_full_code(self):
- """A real P2S fault from #2728; the reporter traced it to a module and
- firmware mismatch, which is what Bambu's text says."""
- assert describe_fault("050003000002000E").startswith(
- "Some modules are incompatible with the printer's firmware version"
- )
- def test_is_case_and_whitespace_tolerant(self):
- assert describe_fault(" 0300400c ") == describe_fault("0300400C") is not None
- def test_a_code_listed_without_text_is_empty_not_missing(self):
- """Two of the reporter's three P2S faults. Bambu lists them and publishes
- no text; lookup_fault keeps that distinct from "not listed", and
- describe_fault reads both as no description."""
- assert lookup_fault("0500060000020070") == ""
- assert lookup_fault("050002000003000A") == ""
- assert describe_fault("0500060000020070") is None
- def test_an_unlisted_code_is_none(self):
- assert lookup_fault("99999999") is None
- assert lookup_fault("FFFFFFFFFFFFFFFF") is None
- def test_the_text_follows_the_printer_model(self):
- """0300_8001 is "paused by the user" on some models and "paused by a
- pause command in the file" on others, and 0300_8013 the other way round.
- A merged table alone would tell one set of owners the wrong thing."""
- on_20p = describe_fault("03008001", "20P")
- on_093 = describe_fault("03008001", "093")
- assert "paused by the user" in on_20p
- assert "pause command" in on_093
- assert describe_fault("03008013", "20P") == on_093
- def test_an_unknown_or_missing_model_uses_the_merged_text(self):
- merged = CATALOG["error"]["03008001"]
- assert describe_fault("03008001", "XXX") == merged
- assert describe_fault("03008001") == merged
- assert describe_fault("03008001", "") == merged
- def test_a_sixteen_char_code_never_falls_back_to_a_print_error_key(self):
- """The old G1_G4 collapse looked an hms[] code up as a print_error. No
- real hms[] code has an error group at or above 0x4000, where every
- print_error key sits, so the collapse could only attach a neighbour's
- sentence. Built here from a real print_error key to prove it's gone."""
- key = "03004000"
- assert describe_fault(key) is not None
- forced = f"{key[:4]}02000001{key[4:]}"
- assert forced not in CATALOG["hms"]
- assert describe_fault(forced) is None
- def test_no_real_hms_code_reaches_the_print_error_range(self):
- """The fact the previous test rests on, checked against the data."""
- assert all(int(code[12:], 16) < 0x4000 for code in CATALOG["hms"])
- def test_empty_missing_or_malformed_is_none(self):
- assert describe_fault("") is None
- assert describe_fault(None) is None
- assert describe_fault("0300") is None
- assert describe_fault("030080040") is None
- def test_short_code_helper_matches_the_full_code(self):
- assert get_error_description("0300_400c") == describe_fault("0300400C")
- assert get_error_description("0300_8001", "20P") == describe_fault("03008001", "20P")
- class TestPrintErrorAlertLevel:
- @pytest.mark.parametrize(
- ("error", "level"),
- [(0x4000, 1), (0x40FF, 1), (0x8004, 2), (0x8FFF, 2), (0xC003, 3), (0xCFFF, 3), (0x0002, 0), (0x5000, 0)],
- )
- def test_maps_the_first_hex_digit(self, error, level):
- """4xxx stops the task, 8xxx pauses it, Cxxx is a prompt -- the hms[]
- levels 1, 2, 3. Anything else has no defined level."""
- assert alert_level_from_print_error(error) == level
|