#!/usr/bin/env python3 """Tests for scripts/orca_id_tool.py (stdlib unittest, no external deps). Run from the repo root: python -m unittest discover -s scripts/tests -v """ import contextlib import io import json import os import re import shutil import sys import tempfile import unittest import uuid sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import orca_id_tool as afi # noqa: E402 import update_bambu_filament_ids as ubfi # noqa: E402 REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) REAL_PROFILES = os.path.join(REPO_ROOT, "resources", "profiles") OFL = "OrcaFilamentLibrary" def load_json_file(path): with open(path, encoding="utf-8") as f: return json.load(f) # --------------------------------------------------------------------------- # helpers: synthetic profile trees # --------------------------------------------------------------------------- def preset(name, filament_id=None, inherits=None, instantiation=True, compatible_printers=None, filament_vendor=None, filament_type=None): data = {"type": "filament", "name": name} if inherits is not None: data["inherits"] = inherits if filament_id is not None: data["filament_id"] = filament_id if filament_vendor is not None: data["filament_vendor"] = ( [filament_vendor] if isinstance(filament_vendor, str) else filament_vendor) if filament_type is not None: data["filament_type"] = ( [filament_type] if isinstance(filament_type, str) else filament_type) data["instantiation"] = "true" if instantiation else "false" if compatible_printers is not None: data["compatible_printers"] = compatible_printers return data class SyntheticTree: """A throwaway resources/profiles-shaped directory plus a snapshot path.""" def __init__(self): self.dir = tempfile.mkdtemp(prefix="filament_id_test_") self.profiles = os.path.join(self.dir, "profiles") os.makedirs(self.profiles) self.snapshot = os.path.join(self.dir, "filament_id_snapshot.json") def cleanup(self): shutil.rmtree(self.dir, ignore_errors=True) def preset_path(self, vendor, name): return os.path.join(self.profiles, vendor, "filament", name + ".json") def add_vendor(self, vendor, presets): vendor_dir = os.path.join(self.profiles, vendor, "filament") os.makedirs(vendor_dir, exist_ok=True) index = {"name": vendor, "version": "01.00.00.00", "filament_list": []} for data in presets: fname = data["name"] + ".json" with open(os.path.join(vendor_dir, fname), "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) index["filament_list"].append( {"name": data["name"], "sub_path": f"filament/{fname}"}) with open(os.path.join(self.profiles, vendor + ".json"), "w", encoding="utf-8") as f: json.dump(index, f, indent=4, ensure_ascii=False) def add_to_index(self, vendor, name): idx_path = os.path.join(self.profiles, vendor + ".json") with open(idx_path, encoding="utf-8") as f: index = json.load(f) index["filament_list"].append( {"name": name, "sub_path": f"filament/{name}.json"}) with open(idx_path, "w", encoding="utf-8") as f: json.dump(index, f, indent=4, ensure_ascii=False) def write_preset(self, vendor, data, register=True): path = self.preset_path(vendor, data["name"]) with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) if register: self.add_to_index(vendor, data["name"]) def set_sub_path(self, vendor, name, sub_path): """Rewrite one index entry's sub_path (the file itself does not move).""" idx_path = os.path.join(self.profiles, vendor + ".json") with open(idx_path, encoding="utf-8") as f: index = json.load(f) for entry in index["filament_list"]: if entry["name"] == name: entry["sub_path"] = sub_path with open(idx_path, "w", encoding="utf-8") as f: json.dump(index, f, indent=4, ensure_ascii=False) def remove_preset(self, vendor, name): os.remove(self.preset_path(vendor, name)) idx_path = os.path.join(self.profiles, vendor + ".json") with open(idx_path, encoding="utf-8") as f: index = json.load(f) index["filament_list"] = [ e for e in index["filament_list"] if e["name"] != name] with open(idx_path, "w", encoding="utf-8") as f: json.dump(index, f, indent=4, ensure_ascii=False) def bytes_map(self): """{relative path -> file bytes} over every .json in the tree.""" raw = {} for root, dirs, files in os.walk(self.profiles): dirs.sort() for name in sorted(files): if not name.endswith(".json"): continue path = os.path.join(root, name) with open(path, "rb") as f: raw[os.path.relpath(path, self.profiles)] = f.read() return raw # -- pipeline wrappers --------------------------------------------------- def update_snapshot(self, dry_run=False): buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = afi.update_snapshot(self.profiles, self.snapshot, dry_run) return rc, buf.getvalue() def check(self, map_path=None): buf = io.StringIO() kwargs = {} if map_path is None else {"map_path": map_path} with contextlib.redirect_stdout(buf): errors = afi.check_filament_ids(self.profiles, self.snapshot, **kwargs) return errors, buf.getvalue() # assign() and remint() are the same one pass over the tree — every filament # ends up with the id its own triple mints, whether that means inserting a # missing key or rewriting a non-conformant one. Both names are kept because # they read differently at the call sites. def assign(self, vendors=None, dry_run=False): buf = io.StringIO() with contextlib.redirect_stdout(buf): changed, errors = afi.generate_filament_ids( self.profiles, self.snapshot, vendors, dry_run) return changed, errors, buf.getvalue() def remint(self, vendors, dry_run=False): buf = io.StringIO() with contextlib.redirect_stdout(buf): changed, errors = afi.generate_filament_ids( self.profiles, self.snapshot, vendors, dry_run) return changed, errors, buf.getvalue() def cli(self, *flags): """Run main() against this tree, capturing stdout.""" buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = afi.main([*flags, "--profiles", self.profiles, "--snapshot", self.snapshot]) return rc, buf.getvalue() def make_clean_tree(apla_id="AX01", generic_id="OGFL99"): """Baseline tree: OFL base+generic, a vendor filament, a clean tuned generic. apla_id/generic_id default to arbitrary non-OF placeholders (sanctioned by the snapshot below) since most tests only need "already assigned, don't touch" and never run the checks. TestAssign and the check tests pass real OF-format ids instead (OfCleanTreeCase). """ t = SyntheticTree() t.add_vendor(OFL, [ preset("Generic PLA @base", filament_id=generic_id, instantiation=False, filament_vendor="Generic", filament_type="PLA"), preset("Generic PLA @System", inherits="Generic PLA @base", compatible_printers=[]), ]) t.add_vendor("VendorA", [ preset("APLA @base", filament_id=apla_id, instantiation=False, filament_vendor="AVendor", filament_type="PLA"), preset("APLA @P1", inherits="APLA @base", compatible_printers=["P1 0.4 nozzle"]), # Correctly tuned OFL generic: keeps the OFL base name, claims a printer. preset("Generic PLA @P1", inherits="Generic PLA @System", compatible_printers=["P1 0.4 nozzle"]), ]) rc, _out = t.update_snapshot() assert rc == 0 return t class SyntheticTreeCase(unittest.TestCase): def setUp(self): self.t = make_clean_tree() self.addCleanup(self.t.cleanup) class OfCleanTreeCase(unittest.TestCase): """Like SyntheticTreeCase, but the baseline filament/generic already carry real OF-format ids (check 1 now rejects "AX01"/"OGFL99" unconditionally, with no snapshot exemption), so an otherwise-untouched tree still passes check_filament_ids. Tests that specifically need a non-OF baseline to remint (TestRemint, TestUpdateSnapshot) keep using SyntheticTreeCase instead. """ def setUp(self): self.t = make_clean_tree( apla_id=afi.generate_filament_id("AVendor", "PLA", "APLA"), generic_id=afi.generate_filament_id("Generic", "PLA", "Generic PLA")) self.addCleanup(self.t.cleanup) # --------------------------------------------------------------------------- # mint # --------------------------------------------------------------------------- class TestMint(unittest.TestCase): def test_namespace_literal(self): # Frozen: derived from the setting_id namespace; baked into the snapshot. self.assertEqual(afi.FILAMENT_ID_NAMESPACE, uuid.UUID("c4d3ff49-4c32-5534-a3e3-00894157ab97")) def test_known_vector(self): # Hardcoded, independently computed vectors: freeze prefix, input string # layout ("filament_product///") and base62 tail. self.assertEqual(afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA"), "OF5CgdDq") self.assertEqual(afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA", salt=1), "OFD9mV8H") self.assertEqual(afi.generate_filament_id("Generic", "PLA", "Generic PLA"), "OFDSrzZ8") def test_determinism_and_format(self): for triple in [("Polymaker", "PLA", "PolyLite PLA"), ("Creality", "PLA", "CR PLA"), ("Generic", "ABS", "Generic ABS"), ("拓竹", "PLA", "拓竹 PLA")]: # unicode components a = afi.generate_filament_id(*triple) b = afi.generate_filament_id(*triple) self.assertEqual(a, b) self.assertRegex(a, r"^OF[0-9A-Za-z]{6}$") self.assertEqual(len(a), 8) def test_every_triple_component_changes_the_id(self): base = afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA") self.assertNotEqual(base, afi.generate_filament_id("Other", "PLA", "PolyLite PLA")) self.assertNotEqual(base, afi.generate_filament_id("Polymaker", "PETG", "PolyLite PLA")) self.assertNotEqual(base, afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA Pro")) def test_salt_changes_id(self): base = afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA") salted = afi.generate_filament_id("Polymaker", "PLA", "PolyLite PLA", salt=1) self.assertNotEqual(base, salted) self.assertRegex(salted, r"^OF[0-9A-Za-z]{6}$") def test_mint_salt_iteration(self): triple = ("Polymaker", "PLA", "PolyLite PLA") taken = {afi.generate_filament_id(*triple, salt=s) for s in range(2)} self.assertEqual(afi.mint_filament_id(*triple, taken), afi.generate_filament_id(*triple, salt=2)) self.assertEqual(afi.mint_filament_id(*triple, set()), afi.generate_filament_id(*triple)) def test_mint_never_returns_a_salt_the_identity_check_rejects(self): # mint_filament_id stops at MAX_CHECK_SALT, the last iteration check 3 # accepts, so the tool can never write an id its own --check rejects. triple = ("Polymaker", "PLA", "PolyLite PLA") all_taken = afi.mint_iterations(triple) self.assertEqual(len(all_taken), afi.MAX_CHECK_SALT + 1) last = afi.mint_filament_id(*triple, all_taken - {afi.generate_filament_id( *triple, salt=afi.MAX_CHECK_SALT)}) self.assertIn(last, afi.mint_iterations(triple)) with self.assertRaises(RuntimeError): afi.mint_filament_id(*triple, all_taken) def test_want_id_policy(self): # The whole id policy: a triple's id is its first mint iteration that no # OTHER product holds, in the tree or in the snapshot. triple = ("Polymaker", "PLA", "PolyLite PLA") other = ("Elegoo", "PETG", "Rapid PETG") salt0 = afi.generate_filament_id(*triple) salt1 = afi.generate_filament_id(*triple, salt=1) def snapshot(**entries): return {"ids": {fid: {"filaments": [], "name": t[2], "filament_type": t[1], "filament_vendor": t[0]} for fid, t in entries.items()}} # nobody holds it: salt 0 self.assertEqual(afi.make_want_id({"triple_sets": {}})(triple), salt0) # the tree holds it under this very triple: reuse is convergence self.assertEqual( afi.make_want_id({"triple_sets": {salt0: {triple}}})(triple), salt0) # Another triple "holds" it, but salt0 is not a mint iteration of THAT # triple, so the holder is a wrong declaration this very run rewrites. # A transient squatter must not push a product off its own id. self.assertEqual( afi.make_want_id({"triple_sets": {salt0: {other}}})(triple), salt0) self.assertEqual( afi.make_want_id({"triple_sets": {salt0: {triple, other}}})(triple), salt0) # The snapshot is read the same way: only a conformant record holds. self.assertEqual( afi.make_want_id({"triple_sets": {}}, snapshot(**{salt0: other}))(triple), salt0) # ... and the snapshot's own triple reuses it self.assertEqual( afi.make_want_id({"triple_sets": {}}, snapshot(**{salt0: triple}))(triple), salt0) # Unused here, but salting must still be reachable: see # test_want_id_salts_past_a_genuine_collision. self.assertNotEqual(salt0, salt1) # memoized: one triple keeps one id for the whole run want_id = afi.make_want_id({"triple_sets": {}}) self.assertEqual(want_id(triple), want_id(triple)) # ... and a second triple never gets the id this run already handed out self.assertNotEqual(want_id(triple), want_id(other)) def test_want_id_salts_past_a_genuine_collision(self): # Salting exists for one case only: two products whose triples mint the # same id. That needs a base62 collision, so force one — salt 0 of every # triple collapses to a single value while the salted iterations stay # distinct. `other` then CONFORMANTLY holds the shared salt-0 id, and # `triple` must step past it instead of stealing it. triple = ("V", "PLA", "X") other = ("W", "ABS", "Y") real = afi.generate_filament_id def colliding(vendor, ftype, name, salt=0): return "OFcolid" if salt == 0 else real(vendor, ftype, name, salt) afi.generate_filament_id = colliding try: self.assertIn("OFcolid", afi.mint_iterations(other)) # holder conforms got = afi.make_want_id({"triple_sets": {"OFcolid": {other}}})(triple) finally: afi.generate_filament_id = real self.assertEqual(got, real(*triple, salt=1)) class TestBaseName(unittest.TestCase): def test_filament_name_derivation(self): cases = [ ("X @base", "X"), ("Afinia PLA@HS", "Afinia PLA"), ("PolyTerra PLA", "PolyTerra PLA"), ("HATCHBOX PLA @Qidi X-Plus 4 0.6 nozzle", "HATCHBOX PLA"), ("A @B @C", "A"), # first @ wins ("Filár PLA 拓竹 @0.4 nozzle", "Filár PLA 拓竹"), ] for name, filament_name in cases: self.assertEqual(afi.base_name(name), filament_name, msg=name) # --------------------------------------------------------------------------- # vendor discovery # --------------------------------------------------------------------------- class TestVendorDiscovery(unittest.TestCase): def test_a_bundle_is_a_subdir_with_a_matching_index(self): # Neither half alone makes a bundle: resources/profiles tracks a "user" # directory with no user.json, and an index without its directory is a # leftover. Both are skipped, tree-wide. t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("VendorA", []) os.makedirs(os.path.join(t.profiles, "user", "filament")) with open(os.path.join(t.profiles, "Orphan.json"), "w", encoding="utf-8") as f: json.dump({"name": "Orphan", "filament_list": []}, f) self.assertEqual(afi.list_vendor_names(t.profiles), ["VendorA"]) self.assertEqual(sorted(afi.analyze_tree(t.profiles)["vendors"]), ["VendorA"]) # --------------------------------------------------------------------------- # resolver (loader-faithful semantics) # --------------------------------------------------------------------------- class TestResolver(unittest.TestCase): @staticmethod def rec(name, filament_id=None, inherits=None): return {"name": name, "filament_id": filament_id, "inherits": inherits} def resolve(self, name, vendor_recs, ofl_recs, **kw): fmap = {r["name"]: r for r in vendor_recs} omap = {r["name"]: r for r in ofl_recs} return afi.resolve_filament_id(name, fmap, omap, **kw) def test_own_id(self): fid, src, entry = self.resolve("A", [self.rec("A", "ID1")], []) self.assertEqual((fid, src, entry), ("ID1", "own", None)) def test_inherited_within_vendor(self): fid, src, entry = self.resolve( "A", [self.rec("A", inherits="B"), self.rec("B", inherits="C"), self.rec("C", "ID3")], []) self.assertEqual((fid, src, entry), ("ID3", "inherited", None)) def test_ofl_fallback(self): # Vendor preset inherits a name that only exists in the OFL map. fid, src, entry = self.resolve( "A", [self.rec("A", inherits="Generic PLA @System")], [self.rec("Generic PLA @System", inherits="fdm_pla"), self.rec("fdm_pla", "OGFL99")]) self.assertEqual(fid, "OGFL99") self.assertEqual(entry, "Generic PLA @System") def test_ofl_stays_in_ofl(self): # Once a chain enters OFL it stays there: a vendor file sharing an # OFL-internal hop's name must not shadow it. fid, _src, entry = self.resolve( "A", [self.rec("A", inherits="ofl_entry"), self.rec("fdm_pla", "WRONG")], [self.rec("ofl_entry", inherits="fdm_pla"), self.rec("fdm_pla", "RIGHT")]) self.assertEqual(fid, "RIGHT") self.assertEqual(entry, "ofl_entry") def test_dead_end_retries_parent_in_ofl(self): # The vendor chain dead-ends id-less on a parent that also exists in # OFL: the loader re-consults the OFL map for that direct parent. fid, _src, entry = self.resolve( "A", [self.rec("A", inherits="shared"), self.rec("shared")], [self.rec("shared", "OFLID1")]) self.assertEqual(fid, "OFLID1") self.assertEqual(entry, "shared") def test_cycle(self): fid, src, _e = self.resolve( "A", [self.rec("A", inherits="B"), self.rec("B", inherits="A")], []) self.assertEqual((fid, src), (None, "cycle")) def test_dangling_parent(self): fid, src, _e = self.resolve("A", [self.rec("A", inherits="nope")], []) self.assertEqual((fid, src), (None, "dangling")) def test_missing_id(self): fid, src, _e = self.resolve("A", [self.rec("A")], []) self.assertEqual((fid, src), (None, "missing")) class TestTripleResolution(unittest.TestCase): @staticmethod def rec(name, inherits=None, filament_vendor=None, filament_type=None): return {"name": name, "inherits": inherits, "filament_vendor": filament_vendor, "filament_type": filament_type} def field(self, name, vendor_recs, ofl_recs, field="filament_vendor"): fmap = {r["name"]: r for r in vendor_recs} omap = {r["name"]: r for r in ofl_recs} return afi.resolve_filament_field(name, field, fmap, omap) def test_own_value_first_element_of_list(self): got = self.field("A", [self.rec("A", filament_vendor=["Poly", "Ignored"])], []) self.assertEqual(got, "Poly") def test_plain_string_value_tolerated(self): got = self.field("A", [self.rec("A", filament_vendor="Poly")], []) self.assertEqual(got, "Poly") def test_inherited_within_vendor(self): got = self.field( "A", [self.rec("A", inherits="B"), self.rec("B", inherits="C"), self.rec("C", filament_vendor=["Poly"])], []) self.assertEqual(got, "Poly") def test_empty_list_keeps_walking(self): got = self.field( "A", [self.rec("A", inherits="B", filament_vendor=[]), self.rec("B", filament_vendor=["Poly"])], []) self.assertEqual(got, "Poly") def test_ofl_fallback(self): got = self.field( "A", [self.rec("A", inherits="Generic PLA @System")], [self.rec("Generic PLA @System", inherits="fdm_pla"), self.rec("fdm_pla", filament_vendor=["Generic"])]) self.assertEqual(got, "Generic") def test_ofl_stays_in_ofl(self): got = self.field( "A", [self.rec("A", inherits="ofl_entry"), self.rec("fdm_pla", filament_vendor=["WRONG"])], [self.rec("ofl_entry", inherits="fdm_pla"), self.rec("fdm_pla", filament_vendor=["RIGHT"])]) self.assertEqual(got, "RIGHT") def test_dead_end_retries_parent_in_ofl(self): got = self.field( "A", [self.rec("A", inherits="shared"), self.rec("shared")], [self.rec("shared", filament_vendor=["Poly"])]) self.assertEqual(got, "Poly") def test_missing_is_empty(self): self.assertEqual(self.field("A", [self.rec("A")], []), "") self.assertEqual(self.field("A", [self.rec("A", inherits="nope")], []), "") def test_resolve_triple(self): recs = [self.rec("MyPLA @base", filament_vendor=["MyVendor"], filament_type=["PLA"]), self.rec("MyPLA @P1", inherits="MyPLA @base")] fmap = {r["name"]: r for r in recs} self.assertEqual(afi.resolve_triple("MyPLA @P1", fmap, {}), ("MyVendor", "PLA", "MyPLA")) # --------------------------------------------------------------------------- # reserved namespaces # --------------------------------------------------------------------------- class TestReservedSpaces(unittest.TestCase): def test_owners(self): # Bambu AMS/RFID catalog: reserved, but no vendor (not even BBL) may declare it self.assertEqual(afi.reserved_space_owner("GFL99"), (True, None)) # Qidi device protocol: reserved, but no vendor may declare it self.assertEqual(afi.reserved_space_owner("QD_X4_PLA"), (True, None)) self.assertEqual(afi.reserved_space_owner("P1234abc"), (True, None)) self.assertEqual(afi.reserved_space_owner("pAbCdEf1"), (True, None)) # case-insensitive self.assertEqual(afi.reserved_space_owner("null"), (True, None)) self.assertEqual(afi.reserved_space_owner("OF5CgdDq"), (False, None)) self.assertEqual(afi.reserved_space_owner("P1234abcd"), (False, None)) # 8 hex chars: not the user space def test_gf_is_reserved_and_ownerless(self): self.assertEqual(afi.reserved_space_owner("GFA00"), (True, None)) # --------------------------------------------------------------------------- # checks on synthetic trees # --------------------------------------------------------------------------- class TestChecks(OfCleanTreeCase): def test_clean_tree_is_silent(self): errors, out = self.t.check() self.assertEqual(errors, 0, out) self.assertNotIn("[ERROR]", out) self.assertNotIn("[WARNING]", out) def test_check1_unknown_non_of_id(self): self.t.write_preset("VendorA", preset("BPLA @base", filament_id="BOGUS_9", instantiation=False, filament_vendor="BV", filament_type="PLA")) self.t.write_preset("VendorA", preset("BPLA @P1", inherits="BPLA @base", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn('is not a minted "OF" id', out) self.assertIn("BOGUS_9", out) def test_check2_new_claim_needs_snapshot_update(self): self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base", compatible_printers=["P2"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn('claim "VendorA/ANEW" is not sanctioned', out) self.assertIn("--update-snapshot", out) def test_check2_vanished_claim_is_stability_error(self): self.t.remove_preset("VendorA", "APLA @P1") errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("stability", out) self.assertIn('"VendorA/APLA"', out) def test_check2_triple_change_needs_snapshot_update(self): apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA") self.t.write_preset("VendorA", preset("APLA @base", filament_id=apla_id, instantiation=False, filament_vendor="AVendor", filament_type="PETG"), register=False) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn('triple "AVendor/PETG/APLA" is not sanctioned', out) self.assertIn('which records "AVendor/PLA/APLA"', out) # Sanctioning the new triple is not enough: the old id is no longer its # mint (check 3, nothing grandfathered) — the identity fix is a re-mint, # reported once on the root, not again under the variant inheriting it. rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 1, out) self.assertIn("does not match the mint of its triple", out) _changed, errors, out = self.t.remint(["VendorA"]) self.assertEqual(errors, 0, out) rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 0, out) def test_check3_of_id_must_match_triple_mint(self): self.t.write_preset("VendorA", preset("BNEW @base", filament_id="OFZZZZZZ", instantiation=False, filament_vendor="BV", filament_type="PLA")) self.t.write_preset("VendorA", preset("BNEW @P1", inherits="BNEW @base", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("does not match the mint of its triple", out) self.assertIn(afi.generate_filament_id("BV", "PLA", "BNEW"), out) def test_check3_salted_mint_is_accepted(self): salted = afi.generate_filament_id("BV", "PLA", "BNEW", salt=3) self.t.write_preset("VendorA", preset("BNEW @base", filament_id=salted, instantiation=False, filament_vendor="BV", filament_type="PLA")) self.t.write_preset("VendorA", preset("BNEW @P1", inherits="BNEW @base", compatible_printers=["P1"])) _errors, out = self.t.check() # check 2 still wants a snapshot update self.assertNotIn("does not match the mint", out) def test_check3_no_grandfathering_of_a_wrong_declaration(self): # Sanctioning the tree does not excuse a declaration from its mint. self.t.write_preset("VendorA", preset("CNEW @base", filament_id="OFZZZZZZ", instantiation=False, filament_vendor="CV", filament_type="PLA")) self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base", compatible_printers=["P1"])) rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 1, out) self.assertIn("does not match the mint of its triple", out) def test_check3_inherited_id_must_be_the_mint_of_own_triple(self): # A preset of another filament inheriting APLA's root takes APLA's id, # which is not the mint of ITS triple (AVendor/PLA/Tuned PLA). self.t.write_preset("VendorA", preset("Tuned PLA @P1", inherits="APLA @base", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn('"Tuned PLA @P1" (VendorA/filament/Tuned PLA @P1.json) inherits ' 'filament_id "%s"' % afi.generate_filament_id("AVendor", "PLA", "APLA"), out) self.assertIn('mints "%s"' % afi.generate_filament_id("AVendor", "PLA", "Tuned PLA"), out) # ... and sanctioning the tree does not excuse it either. rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 1, out) def test_check3_renamed_tuned_generic_is_an_identity_error(self): # Riding the OFL generic under another base name: same rule, same error. self.t.write_preset("VendorA", preset("Tuned PLA @P1", inherits="Generic PLA @System", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("Tuned PLA @P1", out) self.assertIn("inherits filament_id", out) def test_check3_own_key_on_an_instantiated_preset_is_fine(self): # Where the id comes from is irrelevant: a variant may carry the key. apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA") self.t.write_preset("VendorA", preset("APLA @P1", filament_id=apla_id, inherits="APLA @base", compatible_printers=["P1 0.4 nozzle"]), register=False) errors, out = self.t.check() self.assertEqual(errors, 0, out) def test_check3_inheriting_a_real_filament_of_another_product_is_fine(self): # A branded product may inherit the OFL generic (an instantiated # preset) for its settings; it declares its own triple's id. fid = afi.generate_filament_id("BV", "PLA", "Branded PLA") self.t.write_preset("VendorA", preset("Branded PLA @P1", filament_id=fid, inherits="Generic PLA @System", filament_vendor="BV", compatible_printers=["P1"])) rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 0, out) # With a wrong key it is a plain mint mismatch: the parent plays no # part in the verdict. drifted = afi.generate_filament_id("AVendor", "PLA", "APLA") self.t.write_preset("VendorA", preset("Branded PLA @P1", filament_id=drifted, inherits="Generic PLA @System", filament_vendor="BV", compatible_printers=["P1"]), register=False) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("does not match the mint of its triple", out) def test_check4_reserved_namespace_claims(self): for fid, marker in [("GFX99", "Bambu AMS/RFID catalog"), ("QD_X_PLA", "composed by the device"), ("P1a2b3c4", "user-custom"), ("null", "user-custom")]: with self.subTest(fid=fid): name = f"R{fid} @base" self.t.write_preset("VendorA", preset(name, filament_id=fid, instantiation=False, filament_vendor="RV", filament_type="PLA")) self.t.write_preset("VendorA", preset(f"R{fid} @P1", inherits=name, compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("reserved id space", out) self.assertIn(marker, out) def test_check3c_unresolvable_instantiated_filament(self): self.t.write_preset("VendorA", preset("DNEW @P1", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("resolves no filament_id", out) self.assertIn("hard load error", out) def test_missing_snapshot_is_an_error(self): os.remove(self.t.snapshot) errors, out = self.t.check() self.assertEqual(errors, 1) self.assertIn("snapshot not found", out) class TestCheck5(OfCleanTreeCase): def test_5a_empty_vendor_is_hard_error(self): fid = afi.generate_filament_id("", "PLA", "NVPLA") self.t.write_preset("VendorA", preset("NVPLA @base", filament_id=fid, instantiation=False, filament_type="PLA")) self.t.write_preset("VendorA", preset("NVPLA @P1", inherits="NVPLA @base", compatible_printers=["P1"])) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("resolves empty filament_vendor", out) self.assertIn('filament_vendor "Generic"', out) # No grandfathering: sanctioning the tree does not silence check 5a. rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 1, out) self.assertIn("resolves empty filament_vendor", out) def test_5b_divergent_filament_triples(self): id1 = afi.generate_filament_id("MV", "PLA", "MPLA") id2 = afi.generate_filament_id("MV", "PETG", "MPLA") self.t.write_preset("VendorA", preset("MPLA @base1", filament_id=id1, instantiation=False, filament_vendor="MV", filament_type="PLA")) self.t.write_preset("VendorA", preset("MPLA @base2", filament_id=id2, instantiation=False, filament_vendor="MV", filament_type="PETG")) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn("divergent triples", out) self.assertIn("MV/PLA/MPLA", out) self.assertIn("MV/PETG/MPLA", out) # No grandfathering: sanctioning the tree does not silence check 5b. rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 1, out) self.assertIn("divergent triples", out) def test_5_cross_bundle_divergence_is_warning_only(self): fid = afi.generate_filament_id("BV", "PETG", "APLA") self.t.add_vendor("VendorB", [ preset("APLA @base", filament_id=fid, instantiation=False, filament_vendor="BV", filament_type="PETG"), preset("APLA @PB", inherits="APLA @base", compatible_printers=["PB 0.4 nozzle"]), ]) rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 0, out) self.assertIn("[WARNING]", out) self.assertIn("across bundles", out) self.assertIn('"APLA"', out) class TestCheck6(OfCleanTreeCase): def _write_map(self, rows): path = os.path.join(self.t.dir, "bambu_filament_ids.json") ubfi.write_map(path, rows, "testcommit", "2026-09-04") return path def _write_raw_map(self, payload): """Write a map write_map() would never produce (hand-edited or mis-generated).""" path = os.path.join(self.t.dir, "bambu_filament_ids.json") with open(path, "w", encoding="utf-8", newline="\n") as f: json.dump(payload, f, indent=2, ensure_ascii=False, sort_keys=True) return path def test_row_triple_must_match_tree(self): fid = afi.generate_filament_id("V", "PLA", "Foo") self.t.write_preset("VendorA", preset("Foo @base", filament_id=fid, instantiation=False, filament_vendor="V", filament_type="PLA")) self.t.write_preset("VendorA", preset("Foo @P1", inherits="Foo @base", compatible_printers=["P1"])) map_path = self._write_map( {fid: {"bambu_id": "GFZ00", "vendor": "V", "type": "PLA", "name": "Bar"}}) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn("regenerate the map", out) def test_non_of_map_key_is_an_error(self): # The map is keyed by OUR ids; a Bambu id in the key column means the # map was generated or hand-edited the wrong way round. map_path = self._write_map({ "GFB00": {"bambu_id": "GFB00", "vendor": "V", "type": "PLA", "name": "Foo"}}) errors, out = self.t.check(map_path) self.assertEqual(errors, 1, out) self.assertIn('"GFB00" is not a minted "OF" id', out) def test_duplicate_bambu_id_is_an_error(self): map_path = self._write_map({ "OFaaaaaa": {"bambu_id": "GFZ00", "vendor": "V", "type": "PLA", "name": "Foo"}, "OFbbbbbb": {"bambu_id": "GFZ00", "vendor": "V", "type": "PETG", "name": "Bar"}, }) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn("GFZ00", out) def test_row_for_unshipped_product_is_fine(self): map_path = self._write_map({ "OFcccccc": {"bambu_id": "GFZ99", "vendor": "Nobody", "type": "PLA", "name": "Ships Nothing"}, }) errors, out = self.t.check(map_path) self.assertEqual(errors, 0, out) def test_non_object_top_level_is_a_clean_error(self): # A hand-edited map that is a list (or any non-object) must report the map, # not raise AttributeError out of the check. map_path = self._write_raw_map([{"bambu_id": "GFZ00"}]) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn("does not parse", out) self.assertIn("regenerate the map", out) def test_empty_filaments_section_is_an_error(self): # What a regeneration against the wrong --bambustudio-dir writes: a well-formed # header with zero rows. At runtime every translation silently becomes identity. map_path = self._write_map({}) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn('no "filaments" rows', out) def test_absent_filaments_section_is_an_error(self): map_path = self._write_raw_map({ "source": "https://github.com/bambulab/BambuStudio", "bambustudio_commit": "testcommit", "generated": "2026-09-04", }) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn('no "filaments" rows', out) def test_row_without_bambu_id_is_an_error(self): # Two such rows used to collide on None and be reported as a duplicate id. map_path = self._write_map({ "OFaaaaaa": {"vendor": "V", "type": "PLA", "name": "Foo"}, "OFbbbbbb": {"vendor": "V", "type": "PETG", "name": "Bar"}, }) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn('"OFaaaaaa" declares no "bambu_id"', out) self.assertIn('"OFbbbbbb" declares no "bambu_id"', out) self.assertNotIn("mapped by both", out) def test_empty_bambu_id_is_an_error(self): # "" would land in the runtime map and translate an empty tray id into a filament. map_path = self._write_map({ "OFaaaaaa": {"bambu_id": "", "vendor": "V", "type": "PLA", "name": "Foo"}, }) errors, out = self.t.check(map_path) self.assertGreater(errors, 0) self.assertIn('declares no "bambu_id"', out) # --------------------------------------------------------------------------- # --update-snapshot # --------------------------------------------------------------------------- class TestUpdateSnapshot(SyntheticTreeCase): def test_idempotent_and_deterministic(self): with open(self.t.snapshot, "rb") as f: first = f.read() rc, out = self.t.update_snapshot() self.assertEqual(rc, 0) self.assertIn("nothing changed", out) with open(self.t.snapshot, "rb") as f: self.assertEqual(f.read(), first) self.assertTrue(first.endswith(b"\n")) self.assertNotIn(b"\r", first) snap = json.loads(first.decode("utf-8")) self.assertEqual(list(snap), ["ids"]) # state only, no exception lists self.assertEqual(list(snap["ids"]), sorted(snap["ids"])) self.assertEqual(snap["ids"]["AX01"], { "filaments": ["VendorA/APLA"], "name": "APLA", "filament_type": "PLA", "filament_vendor": "AVendor"}) self.assertEqual(snap["ids"]["OGFL99"], { "filaments": ["OrcaFilamentLibrary/Generic PLA", "VendorA/Generic PLA"], "name": "Generic PLA", "filament_type": "PLA", "filament_vendor": "Generic"}) # Key order is part of the on-disk format. self.assertEqual(list(snap["ids"]["AX01"]), ["filaments", "name", "filament_type", "filament_vendor"]) def test_refuses_a_tree_it_could_not_read(self): # A bundle that does not parse contributes no ids, so sanctioning the # rest would record the loss as a deliberate removal. with open(self.t.snapshot, "rb") as f: before = f.read() with open(os.path.join(self.t.profiles, "VendorA", "filament", "APLA @base.json"), "w", encoding="utf-8") as f: f.write("{ not json") rc, out = self.t.update_snapshot() self.assertEqual(rc, 1, out) self.assertIn("unreadable filament profile", out) with open(self.t.snapshot, "rb") as f: self.assertEqual(f.read(), before) def test_refuses_an_id_declared_under_two_triples(self): # VendorB re-declares APLA's id for a different product: one id, two # triples. No single entry can describe it, and check 3 rejects it anyway. self.t.add_vendor("VendorB", [ preset("BPLA @base", filament_id="AX01", instantiation=False, filament_vendor="BVendor", filament_type="PLA"), preset("BPLA @P1", inherits="BPLA @base", compatible_printers=["P1"]), ]) with open(self.t.snapshot, "rb") as f: before = f.read() rc, out = self.t.update_snapshot() self.assertEqual(rc, 1) self.assertIn('refusing to sanction filament_id "AX01": declared under 2 triples ' '(AVendor/PLA/APLA; BVendor/PLA/BPLA)', out) with open(self.t.snapshot, "rb") as f: self.assertEqual(f.read(), before) # nothing written on refusal def test_refuses_reserved_namespace_ids(self): self.t.write_preset("VendorA", preset("CNEW @base", filament_id="GFX99", instantiation=False, filament_vendor="CV", filament_type="PLA")) self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base", compatible_printers=["P1"])) with open(self.t.snapshot, "rb") as f: before = f.read() rc, out = self.t.update_snapshot() self.assertEqual(rc, 1) self.assertIn("refusing to sanction", out) with open(self.t.snapshot, "rb") as f: self.assertEqual(f.read(), before) # nothing written on refusal def test_dry_run_reports_without_writing(self): self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base", compatible_printers=["P2"])) with open(self.t.snapshot, "rb") as f: before = f.read() rc, out = self.t.update_snapshot(dry_run=True) self.assertEqual(rc, 0) self.assertIn("would be rewritten", out) self.assertIn("claims added : 1", out) with open(self.t.snapshot, "rb") as f: self.assertEqual(f.read(), before) # the real run writes exactly what the dry run reported rc, out = self.t.update_snapshot() self.assertEqual(rc, 0) self.assertIn("snapshot written", out) snap = load_json_file(self.t.snapshot) self.assertEqual(snap["ids"]["AX01"]["filaments"], ["VendorA/ANEW", "VendorA/APLA"]) # --------------------------------------------------------------------------- # --generate: one rule for inserts and rewrites alike # --------------------------------------------------------------------------- class TestAssign(OfCleanTreeCase): def test_noop_on_fully_idded_tree(self): changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (0, 0)) self.assertIn("filament_ids inserted : 0", out) self.assertIn("filament_ids re-minted : 0", out) def test_mints_into_filament_root_and_rootless_member(self): self.t.write_preset("VendorA", preset("FNEW @base", instantiation=False, filament_vendor="FV", filament_type="PLA")) self.t.write_preset("VendorA", preset("FNEW @P1", inherits="FNEW @base", compatible_printers=["P1"])) self.t.write_preset("VendorA", preset("FNEW @P2", inherits="FNEW @base", compatible_printers=["P2"])) self.t.write_preset("VendorA", preset("GNEW @P1", compatible_printers=["P1"], filament_vendor="GV", filament_type="PETG")) changed, errors, _out = self.t.assign() self.assertEqual(errors, 0) self.assertEqual(changed, 2) # one root + one root-less member root = load_json_file(self.t.preset_path("VendorA", "FNEW @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id("FV", "PLA", "FNEW")) member = load_json_file(self.t.preset_path("VendorA", "GNEW @P1")) self.assertEqual(member["filament_id"], afi.generate_filament_id("GV", "PETG", "GNEW")) # variants themselves never get the key child = load_json_file(self.t.preset_path("VendorA", "FNEW @P1")) self.assertNotIn("filament_id", child) # idempotent: second run is a no-op changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (0, 0)) self.assertIn("filament_ids inserted : 0", out) def test_refuses_filament_with_incomplete_triple(self): self.t.write_preset("VendorA", preset("ENEW @base", instantiation=False)) self.t.write_preset("VendorA", preset("ENEW @P1", inherits="ENEW @base", compatible_printers=["P1"])) changed, errors, out = self.t.assign() self.assertEqual(changed, 0) self.assertGreater(errors, 0) self.assertIn("resolves empty filament_vendor and filament_type", out) self.assertIn("mint key needs both", out) def test_refuses_to_re_mint_a_declaration_with_an_incomplete_triple(self): # The rewrite half of the same guard: a declared id that is not its # triple's mint still cannot be re-derived without a filament_vendor. self.t.write_preset("VendorA", preset("QNEW @base", filament_id="OFZZZZZZ", instantiation=False, filament_type="PLA")) self.t.write_preset("VendorA", preset("QNEW @P1", inherits="QNEW @base", compatible_printers=["P1"])) before = self.t.bytes_map() changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (0, 1), out) self.assertIn("resolves empty filament_vendor", out) self.assertIn("mint key needs both", out) self.assertEqual(self.t.bytes_map(), before) def test_refuses_filament_with_divergent_root_fields(self): self.t.write_preset("VendorA", preset("HNEW @base1", instantiation=False, filament_vendor="HV", filament_type="PLA")) self.t.write_preset("VendorA", preset("HNEW @base2", instantiation=False, filament_vendor="HV", filament_type="PETG")) self.t.write_preset("VendorA", preset("HNEW @P1", inherits="HNEW @base1", compatible_printers=["P1"])) self.t.write_preset("VendorA", preset("HNEW @P2", inherits="HNEW @base2", compatible_printers=["P2"])) changed, errors, out = self.t.assign() self.assertEqual(changed, 0) self.assertGreater(errors, 0) self.assertIn("divergent (filament_vendor, filament_type)", out) def test_parent_of_another_filament_never_receives_the_key(self): # Members whose id-less parent belongs to another filament (here one # parent shared by two filaments) carry the key themselves: the # parent's own triple would mint a different id (check 3). self.t.write_preset("VendorA", preset("shared_base", instantiation=False, filament_vendor="SV", filament_type="PLA")) self.t.write_preset("VendorA", preset("HNEW @P1", inherits="shared_base", compatible_printers=["P1"])) self.t.write_preset("VendorA", preset("INEW @P1", inherits="shared_base", compatible_printers=["P1"])) changed, errors, out = self.t.assign() self.assertEqual(errors, 0, out) self.assertEqual(changed, 2) for filament_name in ("HNEW", "INEW"): member = load_json_file(self.t.preset_path("VendorA", f"{filament_name} @P1")) self.assertEqual(member["filament_id"], afi.generate_filament_id("SV", "PLA", filament_name)) parent = load_json_file(self.t.preset_path("VendorA", "shared_base")) self.assertNotIn("filament_id", parent) # ... and the tree they leave behind passes the identity check. rc, _out = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 0, out) def test_non_of_declaration_is_re_minted(self): # A declaration that isn't OF-format (e.g. a vendor bundle synced from # an upstream catalog, like BBL's GF ids) is not the mint of its own # triple, so the same pass rewrites it in place. self.t.write_preset("VendorA", preset("Synced PLA @base", filament_id="GFZZ00", instantiation=False, filament_vendor="ZV", filament_type="PLA")) self.t.write_preset("VendorA", preset("Synced PLA @P1", inherits="Synced PLA @base", compatible_printers=["P1"])) changed, errors, out = self.t.assign() self.assertEqual(errors, 0, out) self.assertEqual(changed, 1) path = self.t.preset_path("VendorA", "Synced PLA @base") root = load_json_file(path) want = afi.generate_filament_id("ZV", "PLA", "Synced PLA") self.assertEqual(root["filament_id"], want) # the value was replaced in place, not appended as a second key with open(path, encoding="utf-8") as f: raw = f.read() self.assertEqual(raw.count('"filament_id"'), 1) # idempotent: the id is its triple's mint now, so a second run is a no-op changed, errors, _out = self.t.assign() self.assertEqual((changed, errors), (0, 0)) def test_non_of_multi_root_filament_converges_on_one_id(self): # Two per-printer roots of one product (same triple), both carrying # the SAME non-OF id — the shape a synced vendor bundle ships (e.g. # BambuStudio's own per-printer @base files). They must converge on # one freshly minted id, not split into two. self.t.write_preset("VendorA", preset("Synced ABS @P1base", filament_id="GFSYNC0", instantiation=False, filament_vendor="ZV", filament_type="ABS")) self.t.write_preset("VendorA", preset("Synced ABS @P2base", filament_id="GFSYNC0", instantiation=False, filament_vendor="ZV", filament_type="ABS")) self.t.write_preset("VendorA", preset("Synced ABS @P1", inherits="Synced ABS @P1base", compatible_printers=["P1"])) self.t.write_preset("VendorA", preset("Synced ABS @P2", inherits="Synced ABS @P2base", compatible_printers=["P2"])) changed, errors, out = self.t.assign() self.assertEqual(errors, 0, out) self.assertEqual(changed, 2) want = afi.generate_filament_id("ZV", "ABS", "Synced ABS") b1 = load_json_file(self.t.preset_path("VendorA", "Synced ABS @P1base")) b2 = load_json_file(self.t.preset_path("VendorA", "Synced ABS @P2base")) self.assertEqual(b1["filament_id"], want) self.assertEqual(b2["filament_id"], want) def test_converges_on_an_existing_tree_id_for_the_same_triple(self): # BEHAVIOUR CHANGE: an id-less filament whose product is already shipped # (with its conforming id) in another bundle converges on that id # instead of salting past it. One product, one id, in every bundle. want = afi.generate_filament_id("CV", "PLA", "CPLA") self.t.add_vendor("VendorB", [ preset("CPLA @base", filament_id=want, instantiation=False, filament_vendor="CV", filament_type="PLA"), preset("CPLA @PB", inherits="CPLA @base", compatible_printers=["PB 0.4 nozzle"]), ]) self.t.write_preset("VendorA", preset("CPLA @base", instantiation=False, filament_vendor="CV", filament_type="PLA")) self.t.write_preset("VendorA", preset("CPLA @P1", inherits="CPLA @base", compatible_printers=["P1"])) changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (1, 0), out) root = load_json_file(self.t.preset_path("VendorA", "CPLA @base")) self.assertEqual(root["filament_id"], want) def test_a_squatted_id_is_still_minted_for_its_own_product(self): # VendorB's "Other" declares the id that belongs to VendorA's "DPLA" — # a copy-paste, not a real claim, since it is not the mint of Other's # own triple. That declaration is transient (--generate rewrites it), so # it must NOT push DPLA onto a salted id: DPLA gets its canonical one. want0 = afi.generate_filament_id("DV", "PLA", "DPLA") self.t.add_vendor("VendorB", [ preset("Other @base", filament_id=want0, instantiation=False, filament_vendor="OV", filament_type="ABS"), ]) self.t.write_preset("VendorA", preset("DPLA @base", instantiation=False, filament_vendor="DV", filament_type="PLA")) self.t.write_preset("VendorA", preset("DPLA @P1", inherits="DPLA @base", compatible_printers=["P1"])) changed, errors, out = self.t.assign(["VendorA"]) # VendorB is outside the write set, so the id DPLA just took is still # declared there: the run says so rather than leaving a silent duplicate. self.assertEqual((changed, errors), (1, 1), out) self.assertIn("declared by VendorB/filament/Other @base.json", out) # ...and keeps saying so until a run that covers VendorB clears it. self.assertEqual(self.t.assign(["VendorA"])[:2], (0, 1)) root = load_json_file(self.t.preset_path("VendorA", "DPLA @base")) self.assertEqual(root["filament_id"], want0) # A full run re-mints Other to its own triple and the duplicate is gone. other = load_json_file(self.t.preset_path("VendorB", "Other @base")) self.assertEqual(other["filament_id"], want0) changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (1, 0), out) other = load_json_file(self.t.preset_path("VendorB", "Other @base")) self.assertEqual(other["filament_id"], afi.generate_filament_id("OV", "ABS", "Other")) def test_mismatching_of_declaration_is_re_derived(self): # BEHAVIOUR CHANGE: the default run used to leave an OF-format id alone # and needed a separate mode to fix it. One rule now: an id that is not # a mint iteration of its own triple is rewritten. self.t.write_preset("VendorA", preset("KNEW @base", filament_id="OFZZZZZZ", instantiation=False, filament_vendor="KV", filament_type="PLA")) self.t.write_preset("VendorA", preset("KNEW @P1", inherits="KNEW @base", compatible_printers=["P1"])) want = afi.generate_filament_id("KV", "PLA", "KNEW") changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (1, 0), out) self.assertIn('"OFZZZZZZ" -> "%s"' % want, out) root = load_json_file(self.t.preset_path("VendorA", "KNEW @base")) self.assertEqual(root["filament_id"], want) changed, errors, _out = self.t.assign() self.assertEqual((changed, errors), (0, 0)) def test_vendor_filter_limits_rewrites_and_inserts_alike(self): # A mismatching declarer in VendorA and an id-less filament in VendorB; # only VendorA is written, and VendorB's bytes are untouched. self.t.write_preset("VendorA", preset("LNEW @base", filament_id="OFZZZZZZ", instantiation=False, filament_vendor="LV", filament_type="PLA")) self.t.write_preset("VendorA", preset("LNEW @P1", inherits="LNEW @base", compatible_printers=["P1"])) self.t.add_vendor("VendorB", [ preset("MNEW @base", instantiation=False, filament_vendor="MV", filament_type="PLA"), preset("MNEW @PB", inherits="MNEW @base", compatible_printers=["PB 0.4 nozzle"]), ]) before = self.t.bytes_map() changed, errors, out = self.t.assign(["VendorA"]) self.assertEqual((changed, errors), (1, 0), out) root = load_json_file(self.t.preset_path("VendorA", "LNEW @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id("LV", "PLA", "LNEW")) after = self.t.bytes_map() for rel, raw in before.items(): if rel.split(os.sep)[0].startswith("VendorB"): self.assertEqual(after[rel], raw, rel) # ... and the deferred half is exactly what a VendorB run then writes changed, errors, out = self.t.assign(["VendorB"]) self.assertEqual((changed, errors), (1, 0), out) b_root = load_json_file(self.t.preset_path("VendorB", "MNEW @base")) self.assertEqual(b_root["filament_id"], afi.generate_filament_id("MV", "PLA", "MNEW")) def test_unknown_vendor_reports_and_writes_nothing(self): # A real insert is pending, so "wrote nothing" means the unknown vendor # aborted the run before any write — not that the tree was already done. self.t.write_preset("VendorA", preset("PNEW @base", instantiation=False, filament_vendor="PV", filament_type="PLA")) self.t.write_preset("VendorA", preset("PNEW @P1", inherits="PNEW @base", compatible_printers=["P1"])) before = self.t.bytes_map() changed, errors, out = self.t.assign(["Nope"]) self.assertEqual((changed, errors), (0, 1)) self.assertIn("unknown vendor", out) self.assertEqual(self.t.bytes_map(), before) # ... and that pending insert is real: a known-vendor run makes it. changed, errors, out = self.t.assign(["VendorA"]) self.assertEqual((changed, errors), (1, 0), out) self.assertEqual( load_json_file(self.t.preset_path("VendorA", "PNEW @base"))["filament_id"], afi.generate_filament_id("PV", "PLA", "PNEW")) def test_dry_run_reports_the_real_run_and_writes_nothing(self): self.t.write_preset("VendorA", preset("NNEW @base", instantiation=False, filament_vendor="NV", filament_type="PLA")) self.t.write_preset("VendorA", preset("NNEW @P1", inherits="NNEW @base", compatible_printers=["P1"])) before = self.t.bytes_map() dry_changed, errors, out = self.t.assign(dry_run=True) self.assertEqual((dry_changed, errors), (1, 0), out) self.assertIn("would insert", out) self.assertEqual(self.t.bytes_map(), before) changed, errors, out = self.t.assign() self.assertEqual((changed, errors), (dry_changed, 0), out) root = load_json_file(self.t.preset_path("VendorA", "NNEW @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id("NV", "PLA", "NNEW")) # --------------------------------------------------------------------------- # byte-preserving profile edits # --------------------------------------------------------------------------- class TestInsertEditing(unittest.TestCase): CRLF_TEXT = ( '{\r\n' '\t"type": "filament",\r\n' '\t"name": "JNEW @base",\r\n' '\t"inherits": "fdm_pla",\r\n' '\t"from": "system",\r\n' '\t"instantiation": "false",\r\n' '\t"filament_type": [\r\n' '\t\t"PLA"\r\n' '\t]\r\n' '}\r\n' ) CRLF_WITH_ID = ( '{\r\n' '\t"type": "filament",\r\n' '\t"name": "JNEW @base",\r\n' '\t"filament_id": "OFold123",\r\n' '\t"instantiation": "false",\r\n' '\t"filament_type": [\r\n' '\t\t"PLA"\r\n' '\t]\r\n' '}\r\n' ) def test_insert_before_instantiation_preserves_bytes(self): text, n = afi.insert_filament_id(self.CRLF_TEXT, "OFabc123") self.assertEqual(n, 1) json.loads(text) inserted = '\t"filament_id": "OFabc123",\r\n' self.assertIn(inserted + '\t"instantiation"', text) # every original byte is preserved: removing the inserted line restores # the input exactly (CRLF stays CRLF, tabs stay tabs) self.assertEqual(text.replace(inserted, "", 1), self.CRLF_TEXT) def test_insert_after_name_when_no_instantiation_line(self): lf_text = '{\n "type": "filament",\n "name": "K",\n "from": "system"\n}\n' text, n = afi.insert_filament_id(lf_text, "OFabc123") self.assertEqual(n, 1) json.loads(text) self.assertIn('"name": "K",\n "filament_id": "OFabc123",\n', text) self.assertNotIn("\r", text) def test_insert_no_anchor_fails(self): _text, n = afi.insert_filament_id('{"type": "filament"}', "OFabc123") self.assertEqual(n, 0) def test_write_filament_id_keeps_crlf_on_disk(self): t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("VendorA", []) path = t.preset_path("VendorA", "JNEW @base") with open(path, "wb") as f: f.write(self.CRLF_TEXT.encode("utf-8")) t.add_to_index("VendorA", "JNEW @base") afi.write_filament_id(path, "OFabc123") with open(path, "rb") as f: raw = f.read() self.assertEqual(raw.count(b"\n"), raw.count(b"\r\n")) # still CRLF-only self.assertEqual( raw.replace(b'\t"filament_id": "OFabc123",\r\n', b"", 1), self.CRLF_TEXT.encode("utf-8")) def test_replace_value_preserves_every_other_byte(self): text, n = afi.replace_filament_id_value(self.CRLF_WITH_ID, "OFold123", "OFnew456") self.assertEqual(n, 1) self.assertEqual(json.loads(text)["filament_id"], "OFnew456") self.assertEqual(text, self.CRLF_WITH_ID.replace('"OFold123"', '"OFnew456"')) def test_replace_value_requires_exact_old_value(self): _text, n = afi.replace_filament_id_value(self.CRLF_WITH_ID, "OFwrong1", "OFnew456") self.assertEqual(n, 0) def test_rewrite_filament_id_on_disk(self): t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("VendorA", []) path = t.preset_path("VendorA", "JNEW @base") with open(path, "wb") as f: f.write(self.CRLF_WITH_ID.encode("utf-8")) afi.rewrite_filament_id(path, "OFold123", "OFnew456") with open(path, "rb") as f: raw = f.read() self.assertEqual(raw, self.CRLF_WITH_ID.replace('"OFold123"', '"OFnew456"') .encode("utf-8")) with self.assertRaises(RuntimeError): afi.rewrite_filament_id(path, "OFold123", "OFxxx999") # stale old id def test_delete_line_with_trailing_comma(self): text, n = afi.delete_key_line(self.CRLF_WITH_ID, "filament_id", "OFold123") self.assertEqual(n, 1) self.assertNotIn("filament_id", json.loads(text)) self.assertEqual(text, self.CRLF_WITH_ID.replace( '\t"filament_id": "OFold123",\r\n', "", 1)) def test_delete_last_property_line(self): lf_text = ('{\n "name": "K @base",\n "instantiation": "false",\n' ' "filament_id": "OFold123"\n}\n') text, n = afi.delete_key_line(lf_text, "filament_id", "OFold123") self.assertEqual(n, 1) data = json.loads(text) self.assertNotIn("filament_id", data) self.assertEqual(data["instantiation"], "false") # the preceding comma goes with it, and nothing else moves self.assertEqual(text, '{\n "name": "K @base",\n' ' "instantiation": "false"\n}\n') def test_dry_run_edits_verify_but_write_nothing(self): t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("VendorA", []) fresh = t.preset_path("VendorA", "JNEW @base") with open(fresh, "wb") as f: f.write(self.CRLF_TEXT.encode("utf-8")) idded = t.preset_path("VendorA", "JOLD @base") with open(idded, "wb") as f: f.write(self.CRLF_WITH_ID.encode("utf-8")) afi.write_filament_id(fresh, "OFabc123", dry_run=True) afi.rewrite_filament_id(idded, "OFold123", "OFnew456", dry_run=True) with open(fresh, "rb") as f: self.assertEqual(f.read(), self.CRLF_TEXT.encode("utf-8")) with open(idded, "rb") as f: self.assertEqual(f.read(), self.CRLF_WITH_ID.encode("utf-8")) # the edit is still applied and verified in memory: only the write is # skipped, so a stale old id fails just as loudly with self.assertRaises(RuntimeError): afi.rewrite_filament_id(idded, "OFstale1", "OFnew456", dry_run=True) # --------------------------------------------------------------------------- # --generate: re-minting non-conformant declarations # --------------------------------------------------------------------------- class TestRemint(SyntheticTreeCase): TRIPLE = ("AVendor", "PLA", "APLA") def test_rewrites_mismatching_declaration(self): want = afi.generate_filament_id(*self.TRIPLE) changed, errors, out = self.t.remint(["VendorA"]) self.assertEqual(errors, 0, out) self.assertEqual(changed, 1) self.assertIn('"AX01" -> "%s"' % want, out) root = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(root["filament_id"], want) # OFL was not part of the run ofl_root = load_json_file(self.t.preset_path(OFL, "Generic PLA @base")) self.assertEqual(ofl_root["filament_id"], "OGFL99") # idempotent: a second run over the same vendor rewrites nothing changed, errors, _out = self.t.remint(["VendorA"]) self.assertEqual((changed, errors), (0, 0)) def test_salted_declaration_survives_remint(self): # A deliberate salt split (a second preset of one product kept on its # own id for per-printer AMS disambiguation) is mint-conformant and # must not be converged back onto salt 0. salt1 = afi.generate_filament_id(*self.TRIPLE, salt=1) self.t.write_preset("VendorA", preset("APLA @legacy", filament_id=salt1, inherits="APLA @base", compatible_printers=["P2 0.4 nozzle"])) changed, errors, out = self.t.remint(["VendorA"]) self.assertEqual(errors, 0, out) self.assertEqual(changed, 1) # only the non-conformant AX01 root legacy = load_json_file(self.t.preset_path("VendorA", "APLA @legacy")) self.assertEqual(legacy["filament_id"], salt1) def test_same_triple_converges_within_run(self): self.t.add_vendor("VendorB", [ preset("APLA @Bbase", filament_id="BX01", instantiation=False, filament_vendor="AVendor", filament_type="PLA"), preset("APLA @PB", inherits="APLA @Bbase", compatible_printers=["PB 0.4 nozzle"]), ]) want = afi.generate_filament_id(*self.TRIPLE) changed, errors, _out = self.t.remint(["VendorA", "VendorB"]) self.assertEqual(errors, 0) self.assertEqual(changed, 2) a = load_json_file(self.t.preset_path("VendorA", "APLA @base")) b = load_json_file(self.t.preset_path("VendorB", "APLA @Bbase")) self.assertEqual(a["filament_id"], want) self.assertEqual(b["filament_id"], want) def test_same_triple_converges_with_existing_tree_id(self): # Another bundle already carries the conforming id for the same triple: # reuse is required, not blocked. want = afi.generate_filament_id(*self.TRIPLE) self.t.add_vendor("VendorB", [ preset("APLA @Bbase", filament_id=want, instantiation=False, filament_vendor="AVendor", filament_type="PLA"), ]) changed, errors, _out = self.t.remint(["VendorA"]) self.assertEqual((changed, errors), (1, 0)) a = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(a["filament_id"], want) def test_not_blocked_by_a_non_conformant_occurrence(self): # VendorB carries APLA's id under a PETG triple of its own, so that # declaration is wrong and this run's job is to rewrite it. It must not # cost VendorA's APLA the id its triple actually mints. want0 = afi.generate_filament_id(*self.TRIPLE) self.t.add_vendor("VendorB", [ preset("BPLA @base", filament_id=want0, instantiation=False, filament_vendor="BV", filament_type="PETG"), ]) changed, errors, out = self.t.remint(["VendorA"]) self.assertEqual((changed, errors), (1, 1), out) self.assertIn("declared by VendorB/filament/BPLA @base.json", out) root = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(root["filament_id"], want0) # Widening the run to both bundles clears it. changed, errors, out = self.t.remint(["VendorA", "VendorB"]) self.assertEqual((changed, errors), (1, 0), out) def test_bbl_is_reminted_like_any_vendor(self): self.t.add_vendor("BBL", [ preset("Bambu ABS @base", filament_id="GFB00", instantiation=False, filament_vendor="Bambu Lab", filament_type="ABS"), preset("Bambu ABS @P1", inherits="Bambu ABS @base", compatible_printers=["P1"]), ]) changed, errors, out = self.t.remint(["BBL"]) self.assertEqual(errors, 0, out) self.assertEqual(changed, 1) root = load_json_file(self.t.preset_path("BBL", "Bambu ABS @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id("Bambu Lab", "ABS", "Bambu ABS")) def test_dry_run_leaves_every_file_untouched(self): before = self.t.bytes_map() changed, errors, out = self.t.remint(["VendorA"], dry_run=True) self.assertEqual((changed, errors), (1, 0), out) self.assertIn("would rewrite", out) self.assertEqual(self.t.bytes_map(), before) # the real run then makes exactly that one change changed, errors, _out = self.t.remint(["VendorA"]) self.assertEqual((changed, errors), (1, 0)) root = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id(*self.TRIPLE)) # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- class TestCli(unittest.TestCase): """main(argv) over a synthetic tree. The clean tree's baseline ids are deliberately non-conformant ("AX01"/"OGFL99"), so a --generate run always has both a filament_id rewrite and setting_id inserts to do.""" def setUp(self): self.t = make_clean_tree() self.addCleanup(self.t.cleanup) def test_bare_invocation_prints_help(self): buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = afi.main([]) self.assertEqual(rc, 0) self.assertIn("usage:", buf.getvalue()) self.assertIn("--generate", buf.getvalue()) # ... and so does any invocation naming no mode: help, and no work before = self.t.bytes_map() rc, out = self.t.cli() self.assertEqual(rc, 0) self.assertIn("usage:", out) self.assertEqual(self.t.bytes_map(), before) def test_another_tree_needs_its_own_snapshot(self): # --profiles retargets the tree, but the sanctioned state of that tree # is not the repo snapshot: checking against it is meaningless and # re-recording into it would overwrite the tracked file. with open(afi.SNAPSHOT_PATH, "rb") as f: repo_snapshot = f.read() for mode in ("--check", "--update-snapshot"): with self.assertRaises(SystemExit) as caught: with contextlib.redirect_stderr(io.StringIO()): afi.main([mode, "--profiles", self.t.profiles]) self.assertEqual(caught.exception.code, 2, mode) with open(afi.SNAPSHOT_PATH, "rb") as f: self.assertEqual(f.read(), repo_snapshot) # Named explicitly, both modes run against that tree. rc, out = self.t.cli("--update-snapshot") self.assertEqual(rc, 0, out) # --generate only reads the snapshot as a list of ids not to reuse, so # it keeps working without one. buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = afi.main(["--dry-run", "--profiles", self.t.profiles]) self.assertEqual(rc, 0, buf.getvalue()) def test_filament_id_and_setting_id_together_are_rejected(self): # Each flag's help promises it skips the other kind, so the pair cannot # quietly mean "both". with self.assertRaises(SystemExit) as caught: with contextlib.redirect_stderr(io.StringIO()): afi.main(["--generate", "--filament-id", "--setting-id", "--profiles", self.t.profiles]) self.assertEqual(caught.exception.code, 2) def test_a_run_with_errors_does_not_report_success(self): # An unreadable profile must not be buried under a green summary line. path = self.t.preset_path("VendorA", "APLA @base") with open(path, "w", encoding="utf-8") as f: f.write("{ not json") rc, out = self.t.cli("--generate") self.assertEqual(rc, 1, out) self.assertIn("error(s)", out) self.assertNotIn("SUCCESS", out) def test_dry_run_alone_previews_generate(self): before = self.t.bytes_map() rc, out = self.t.cli("--dry-run") self.assertEqual(rc, 0, out) self.assertIn("would ", out) self.assertIn("nothing written", out) self.assertEqual(self.t.bytes_map(), before) def test_the_reported_count_is_files_not_edits(self): # A file both passes touch is still one file. "SoloPLA @P1" needs a # filament_id rewrite AND a setting_id insert, so an edit-counting # summary would over-report the tree. self.t.write_preset("VendorA", preset( "SoloPLA @P1", filament_id="AX01", filament_vendor="AVendor", filament_type="PLA", compatible_printers=["P1"])) before = self.t.bytes_map() rc, out = self.t.cli("--generate") self.assertEqual(rc, 0, out) after = self.t.bytes_map() touched = {p for p in after if after[p] != before.get(p)} self.assertIn("VendorA/filament/SoloPLA @P1.json".replace("/", os.sep), touched) self.assertIn("rewrite VendorA/filament/SoloPLA @P1.json", out) self.assertIn("update VendorA/filament/SoloPLA @P1.json", out) summary = [l for l in out.splitlines() if "file(s) changed" in l] self.assertEqual(len(summary), 1, out) self.assertIn(f"{len(touched)} file(s) changed", summary[0]) def test_dryrun_is_the_same_flag(self): before = self.t.bytes_map() rc, out = self.t.cli("--dryrun") self.assertEqual(rc, 0, out) self.assertIn("would ", out) # the same preview, not a silent no-op self.assertIn("nothing written", out) self.assertEqual(self.t.bytes_map(), before) def test_generate_vendor_writes_only_in_that_bundle(self): before = self.t.bytes_map() rc, out = self.t.cli("--generate", "--vendor", "VendorA") self.assertEqual(rc, 0, out) after = self.t.bytes_map() changed = sorted(rel for rel in before if after[rel] != before[rel]) self.assertTrue(changed, out) for rel in changed: self.assertTrue(rel.startswith("VendorA" + os.sep), rel) # The bundles it spared were not simply already conformant: the # un-narrowed run goes on to write in them too. rc, out = self.t.cli("--generate") self.assertEqual(rc, 0, out) final = self.t.bytes_map() self.assertTrue(any(final[rel] != after[rel] for rel in after if not rel.startswith("VendorA" + os.sep)), out) def test_generate_unknown_vendor_returns_1(self): before = self.t.bytes_map() rc, out = self.t.cli("--generate", "--vendor", "Nope") self.assertEqual(rc, 1) self.assertIn("unknown vendor", out) self.assertEqual(self.t.bytes_map(), before) def test_setting_id_only_leaves_filament_ids_alone(self): rc, out = self.t.cli("--generate", "--setting-id") self.assertEqual(rc, 0, out) root = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(root["filament_id"], "AX01") # not re-minted member = load_json_file(self.t.preset_path("VendorA", "APLA @P1")) self.assertNotIn("filament_id", member) self.assertEqual(member["setting_id"], afi.generate_preset_setting_id("VendorA", "filament", "APLA @P1")) def test_filament_id_only_inserts_no_setting_id(self): rc, out = self.t.cli("--generate", "--filament-id") self.assertEqual(rc, 0, out) root = load_json_file(self.t.preset_path("VendorA", "APLA @base")) self.assertEqual(root["filament_id"], afi.generate_filament_id("AVendor", "PLA", "APLA")) for name in ("APLA @base", "APLA @P1"): self.assertNotIn( "setting_id", load_json_file(self.t.preset_path("VendorA", name))) def test_check_mode_returns_1_on_errors(self): # What CI keys off: --check exits nonzero when the tree does not match # the snapshot it is validated against. before = self.t.bytes_map() rc, out = self.t.cli("--check") self.assertEqual(rc, 1) self.assertIn("error(s)", out) self.assertEqual(self.t.bytes_map(), before) # --check never writes def test_removed_and_conflicting_flags_are_rejected(self): for argv in (["--remint", "VendorA"], # removed mode ["--mint", "A/B/C"], # removed mode ["--drop-redundant-ids", "VendorA"], # removed mode ["--assign"], # removed mode ["--generate", "--check"], # two modes ["--vendor", "VendorA"], # narrowing without a mode ["--filament-id"], # narrowing without a mode ["--check", "--vendor", "VendorA"]): # narrowing on --check with self.subTest(argv=argv): with self.assertRaises(SystemExit) as cm, \ contextlib.redirect_stdout(io.StringIO()), \ contextlib.redirect_stderr(io.StringIO()): afi.main([*argv, "--profiles", self.t.profiles]) self.assertEqual(cm.exception.code, 2) # --------------------------------------------------------------------------- # the real tree # --------------------------------------------------------------------------- @unittest.skipUnless(os.path.isdir(REAL_PROFILES), "resources/profiles not present") class TestRealTree(unittest.TestCase): def test_shipped_snapshot_matches_tree(self): buf = io.StringIO() with contextlib.redirect_stdout(buf): errors = afi.check_filament_ids(REAL_PROFILES) self.assertEqual(errors, 0, buf.getvalue()) def test_check_cli_returns_0(self): # The exact CI invocation, return code included. buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = afi.main(["--check"]) self.assertEqual(rc, 0, buf.getvalue()) def test_every_instantiated_filament_resolves_an_id(self): analysis = afi.analyze_tree(REAL_PROFILES) self.assertEqual(analysis["missing_effective"], []) self.assertEqual(analysis["read_errors"], []) # --------------------------------------------------------------------------- # review-fix regressions # --------------------------------------------------------------------------- class TestReviewFixes(OfCleanTreeCase): def test_one_file_reached_by_two_spellings_counts_once(self): # The filament pass reaches a file through its index sub_path, the # setting_id pass through os.walk. Those two spellings differ whenever # the sub_path is not already normalized - always, on Windows, where # every sub_path keeps the "/" the index stores. One file is one file. # One preset per write path: SoloPLA has no id (inserted), WrongPLA # declares one that is not its own mint (rewritten). Both are # instantiated, so the setting_id pass reaches them too. self.t.write_preset("VendorA", preset("SoloPLA @P1", compatible_printers=["P1 0.4 nozzle"], filament_vendor="SV", filament_type="PLA")) self.t.write_preset("VendorA", preset( "WrongPLA @P1", filament_id=afi.generate_filament_id("WV", "PLA", "Nope"), compatible_printers=["P1 0.4 nozzle"], filament_vendor="WV", filament_type="PLA")) for name in ("SoloPLA @P1", "WrongPLA @P1"): self.t.set_sub_path("VendorA", name, f"filament/./{name}.json") touched = set() buf = io.StringIO() with contextlib.redirect_stdout(buf): afi.generate_filament_ids(self.t.profiles, self.t.snapshot, None, False, touched) afi.generate_setting_ids(self.t.profiles, None, False, touched) for name in ("SoloPLA @P1", "WrongPLA @P1"): self.assertEqual([t for t in touched if t.endswith(name + ".json")], [self.t.preset_path("VendorA", name)], sorted(touched)) self.assertEqual(sorted(touched), sorted(os.path.normpath(t) for t in touched)) def test_a_broken_edit_names_the_file(self): # An edit that produces invalid JSON is refused, and the message has to # say which of ~12,000 files it was: a bare JSONDecodeError does not. path = self.t.preset_path("VendorA", "APLA @base") with self.assertRaises(RuntimeError) as caught: afi._edit_profile(path, lambda text: (text.replace("{", "{,", 1), 1), what="test edit") self.assertIn(path, str(caught.exception)) self.assertIn("test edit", str(caught.exception)) def test_check3_skips_of_id_inherited_from_other_vendor(self): # An OFL filament carries its own minted OF id and a vendor tunes it # correctly (same base name, non-empty printers). The new claim must # trip only the snapshot gate, never mint conformance. fid = afi.generate_filament_id("Generic", "PLA", "Generic PLA Matte") self.t.write_preset(OFL, preset("Generic PLA Matte @base", filament_id=fid, instantiation=False, filament_vendor="Generic", filament_type="PLA")) self.t.write_preset(OFL, preset("Generic PLA Matte @System", inherits="Generic PLA Matte @base", compatible_printers=[])) rc, _ = self.t.update_snapshot() self.assertEqual(rc, 0) self.t.write_preset("VendorA", preset("Generic PLA Matte @P1", inherits="Generic PLA Matte @System", compatible_printers=["P1 0.4 nozzle"])) errors, out = self.t.check() self.assertNotIn("does not match the mint", out) self.assertIn("not sanctioned", out) self.assertEqual(errors, 1, out) # After sanctioning the claim the tree is fully green again. rc, _ = self.t.update_snapshot() self.assertEqual(rc, 0) errors, out = self.t.check() self.assertEqual(errors, 0, out) def test_check3c_prints_expected_mint(self): self.t.write_preset("VendorA", preset("Orphan PLA @P1", compatible_printers=["P1 0.4 nozzle"], filament_vendor="OV", filament_type="PLA")) errors, out = self.t.check() self.assertGreater(errors, 0) self.assertIn(afi.generate_filament_id("OV", "PLA", "Orphan PLA"), out) # --------------------------------------------------------------------------- # scripts/update_bambu_filament_ids.py: the generated Bambu catalog id map # --------------------------------------------------------------------------- class TestBambuMap(unittest.TestCase): def _bs_tree(self, filaments): # filaments: list of (name, bambu_id, vendor, type); builds a minimal BBL # bundle with an @base per filament carrying the id and one instantiated child. t = SyntheticTree() self.addCleanup(t.cleanup) presets = [] for filament_name, bambu_id, vendor, ftype in filaments: presets.append(preset(f"{filament_name} @base", filament_id=bambu_id, instantiation=False, filament_vendor=vendor, filament_type=ftype)) presets.append(preset(f"{filament_name} @P1", inherits=f"{filament_name} @base", compatible_printers=["P1 0.4 nozzle"])) t.add_vendor("BBL", presets) filaments, errors = afi.load_vendor_filaments(t.profiles, "BBL") self.assertEqual(errors, []) return filaments def test_one_row_per_filament(self): rows = ubfi.derive_rows(self._bs_tree([("Bambu ABS", "GFB00", "Bambu Lab", "ABS")]), {}) self.assertEqual(rows, {afi.generate_filament_id("Bambu Lab", "ABS", "Bambu ABS"): {"bambu_id": "GFB00", "vendor": "Bambu Lab", "type": "ABS", "name": "Bambu ABS"}}) def test_shared_bambu_id_is_an_error(self): with self.assertRaises(SystemExit): ubfi.derive_rows(self._bs_tree([("A", "GFX00", "V", "PLA"), ("B", "GFX00", "V", "PLA")]), {}) def test_reuses_the_id_we_ship_for_that_triple(self): rows = ubfi.derive_rows(self._bs_tree([("Bambu ABS", "GFB00", "Bambu Lab", "ABS")]), {("Bambu Lab", "ABS", "Bambu ABS"): "OFsalted1"}) self.assertIn("OFsalted1", rows) @unittest.skipUnless(os.path.isdir("/Users/lijiang/codes/BambuStudio/resources/profiles"), "no local clone") def test_local_clone_yields_the_catalog(self): rows = ubfi.derive_rows(afi.load_vendor_filaments("/Users/lijiang/codes/BambuStudio/resources/profiles", "BBL")[0], {}) self.assertEqual(len(rows), 100) self.assertEqual(len({r["bambu_id"] for r in rows.values()}), 100) class TestOrcaTriplesFromAnalysis(unittest.TestCase): def _tree(self): t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("VendorA", [ # Same triple declared under two different ids on purpose: mirrors # a real, sanctioned case (Cubicon's xCeler line keeps its own id # per printer instead of inheriting the filament's @base id). # Ambiguous, but must stay harmless unless something needs it. preset("Ambig PLA @base", filament_id="OF111111", instantiation=False, filament_vendor="V", filament_type="PLA"), preset("Ambig PLA @P1", filament_id="OF222222", inherits="Ambig PLA @base", compatible_printers=["P1"]), # An unambiguous filament elsewhere in the same tree. preset("Needed PLA @base", filament_id="OF333333", instantiation=False, filament_vendor="V2", filament_type="PLA"), preset("Needed PLA @P1", inherits="Needed PLA @base", compatible_printers=["P1"]), ]) return afi.analyze_tree(t.profiles) def test_ambiguity_outside_needed_triples_is_ignored(self): analysis = self._tree() result = ubfi.orca_triples_from_analysis(analysis, {("V2", "PLA", "Needed PLA")}) self.assertEqual(result, {("V2", "PLA", "Needed PLA"): "OF333333"}) def test_ambiguity_inside_needed_triples_is_an_error(self): analysis = self._tree() with self.assertRaises(SystemExit): ubfi.orca_triples_from_analysis(analysis, {("V", "PLA", "Ambig PLA")}) class TestWriteMap(unittest.TestCase): def test_format(self): d = tempfile.mkdtemp(prefix="bambu_map_test_") self.addCleanup(shutil.rmtree, d, ignore_errors=True) # nested, not-yet-existing directory: write_map must create it path = os.path.join(d, "out", "bambu_filament_ids.json") rows = {"OFabc123": {"bambu_id": "GFB00", "vendor": "Bambu Lab", "type": "ABS", "name": "Bambu ABS"}} ubfi.write_map(path, rows, "66e405477", "2026-09-04") with open(path, "rb") as f: raw = f.read() self.assertTrue(raw.endswith(b"\n")) self.assertNotIn(b"\r", raw) text = raw.decode("utf-8") self.assertEqual(json.loads(text), { "source": "https://github.com/bambulab/BambuStudio", "bambustudio_commit": "66e405477", "generated": "2026-09-04", "filaments": rows, }) # sorted top-level keys self.assertLess(text.index('"bambustudio_commit"'), text.index('"filaments"')) self.assertLess(text.index('"filaments"'), text.index('"generated"')) self.assertLess(text.index('"generated"'), text.index('"source"')) class TestDriftReport(unittest.TestCase): def test_reports_both_directions(self): t = SyntheticTree() self.addCleanup(t.cleanup) t.add_vendor("BBL", [ preset("Match PLA @base", filament_id="GFX01", instantiation=False, filament_vendor="V", filament_type="PLA"), preset("Match PLA @P1", inherits="Match PLA @base", compatible_printers=["P1"]), preset("Orphan PLA @base", filament_id="GFX03", instantiation=False, filament_vendor="V", filament_type="PLA"), preset("Orphan PLA @P1", inherits="Orphan PLA @base", compatible_printers=["P1"]), ]) rows = { # matches the BBL bundle's "Match PLA" filament: no drift either way "OFmatch01": {"bambu_id": "GFX01", "vendor": "V", "type": "PLA", "name": "Match PLA"}, # no filament of this identity in the BBL bundle above "OFghost01": {"bambu_id": "GFX02", "vendor": "V", "type": "PLA", "name": "Upstream Only PLA"}, } orca_analysis = afi.analyze_tree(t.profiles) lines = ubfi.drift_report(rows, orca_analysis) report = "\n".join(lines) self.assertIn("Upstream Only PLA", report) self.assertIn("Orca BBL filaments with no row: 1", report) self.assertNotIn("Match PLA", report) # the matched filament is not drift if __name__ == "__main__": unittest.main()