54 lines
2.0 KiB
Python
54 lines
2.0 KiB
Python
"""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"] == ""
|