"""Canonical serializer + INV-12 round-trip lock (SD-0002 §6.5.5, §6.8).""" import csv import io from app.domains.products import serialize from app.domains.products.diff import CatalogImage, CatalogProduct, CatalogVariant def _product(**kw) -> CatalogProduct: """A minimal no-option catalog product (one all-NULL variant).""" base = dict( id=1, handle="moon-mug", title="Moon Mug", option_names=(None, None, None), fields={ "title": "Moon Mug", "description_html": None, "vendor": "Acme", "product_type": "standalone", "google_product_category": None, "tags": [], "status": "active", "published": True, }, variants=[CatalogVariant( id=1, options=(None, None, None), position=1, fields={"sku": "WG-MUG", "barcode": None, "price": None, "cost": None, "weight": None, "weight_unit": None, "volume": None, "volume_unit": None, "tax_id_1": None, "tax_id_2": None, "inventory_tracker": None, "inventory_qty": None, "variant_image": None})], images=[], ) base.update(kw) return CatalogProduct(**base) def _rows(products) -> list[dict]: text = "".join(serialize.catalog_to_csv(products)) return list(csv.DictReader(io.StringIO(text))) def test_header_is_full_canonical_set(): text = "".join(serialize.catalog_to_csv([_product()])) header = next(csv.reader(io.StringIO(text))) # Handle + Title first; every known column present exactly once. assert header[0] == "Handle" assert "Title" in header and "Variant SKU" in header and "Image Src" in header assert len(header) == len(set(header)) def test_single_no_option_product_one_row(): rows = _rows([_product()]) assert len(rows) == 1 assert rows[0]["Handle"] == "moon-mug" assert rows[0]["Title"] == "Moon Mug" assert rows[0]["Variant SKU"] == "WG-MUG" # A no-option product emits no option values. assert rows[0]["Option1 Value"] == ""