diff --git a/recognization/code/rebuild_catalog.py b/recognization/code/rebuild_catalog.py index 5682858..4d05406 100644 --- a/recognization/code/rebuild_catalog.py +++ b/recognization/code/rebuild_catalog.py @@ -484,6 +484,10 @@ def main(argv: Optional[Sequence[str]] = None) -> int: print("eval-session check cannot cover image(s) with no capture_session_id: " + ", ".join(unlabelled), file=sys.stderr) + # Read the shelf list before anything is written: a bad path or a + # malformed list must fail here, with the tool's own message, rather + # than after write_catalog has already replaced the catalog. + shelf_skus = load_shelf_skus(args.shelf_tags) engine = build_ocr_engine(args.ocr) ocr_engine = engine if engine.available() else None if ocr_engine is None: @@ -505,8 +509,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: # and vetoes it from the CSV alone, so it is a coverage gap, not a refusal. text_only = sorted(set(names) - set(kept)) siblings = text_discriminated_siblings(kept, args.source, names) - small = below_pyramid_floor(kept, args.source) - verdict = live_ready_verdict(kept, names, load_shelf_skus(args.shelf_tags)) + verdict = live_ready_verdict(kept, names, shelf_skus) report = { "source": args.source, "output": args.output, diff --git a/recognization/code/test_rebuild_catalog.py b/recognization/code/test_rebuild_catalog.py index 32fb330..15b8993 100644 --- a/recognization/code/test_rebuild_catalog.py +++ b/recognization/code/test_rebuild_catalog.py @@ -142,5 +142,47 @@ class ShelfTagListTests(unittest.TestCase): load_shelf_skus(self.write('{"sku_a": 1}')) +class BadShelfListFailsBeforeTheSwapTests(unittest.TestCase): + """A bad --shelf-tags must not leave a replaced catalog behind. + + The list is read before write_catalog runs, so a malformed file or a missing + path fails with the tool's own message and the destination is untouched. Read + after the swap, the same error would escape as a traceback over a catalog + that had already been replaced. + """ + + def make_source(self): + root = tempfile.mkdtemp(prefix="guard-src-") + cv2.imwrite(os.path.join(root, "sku_x1.jpg"), artwork()) + with open(os.path.join(root, "stock.csv"), "w", encoding="utf-8") as handle: + handle.write("SKU_ID,product_name,price,number_in,number_out,number_on\n") + handle.write("sku_x1,Alpha,100,1,0,1\n") + return root + + def run_tool(self, shelf_tags): + from rebuild_catalog import main + + source = self.make_source() + output = os.path.join(tempfile.mkdtemp(prefix="guard-out-"), "catalog") + with self.assertRaises(SystemExit) as caught: + main(["--source", source, "--output", output, "--csv", + os.path.join(source, "stock.csv"), "--min-views", "1", + "--ocr", "none", "--shelf-tags", shelf_tags]) + return str(caught.exception), output + + def test_a_malformed_list_fails_cleanly_and_writes_nothing(self): + path = os.path.join(tempfile.mkdtemp(prefix="bad-tags-"), "tags.json") + with open(path, "w", encoding="utf-8") as handle: + handle.write('{"sku_x1": 1}') + message, output = self.run_tool(path) + self.assertIn("expected a JSON list", message) + self.assertFalse(os.path.exists(output), "the catalog was replaced before the check") + + def test_a_missing_list_fails_cleanly_and_writes_nothing(self): + message, output = self.run_tool("/tmp/definitely-not-a-shelf-list.json") + self.assertIn("No such file", message) + self.assertFalse(os.path.exists(output), "the catalog was replaced before the check") + + if __name__ == "__main__": unittest.main()