feat(products): diff resolves hosted image URLs to existing records — INV-12 over images (SD-0002 §6.3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:19:23 -07:00
parent 906bc87c96
commit 984dc98dee
3 changed files with 90 additions and 6 deletions
+11 -4
View File
@@ -166,18 +166,23 @@ def _gen_catalog(seed: int) -> dict:
fields={"sku": f"SKU-{pid}", "variant_image": None}))
images = []
for ii in range(rng.randint(0, 3)):
# Mix fetched and non-fetched images: a fetched image exports its
# hosted /images/{id}/detail URL, which the diff pre-pass resolves
# back to the same id -> still a no-op (INV-12 over hosted images).
status = "fetched" if rng.random() < 0.5 else "pending"
images.append(CatalogImage(
id=pid * 10 + ii, source_url=f"https://img/{handle}-{ii}.jpg",
position=ii + 1, alt_text=rng.choice([None, f"alt {ii}"])))
position=ii + 1, alt_text=rng.choice([None, f"alt {ii}"]),
status=status))
catalog[handle] = CatalogProduct(
id=pid, handle=handle, title=fields["title"], option_names=option_names,
fields=fields, variants=variants, images=images)
return catalog
def _roundtrip_diff(catalog: dict) -> diff.DiffResult:
def _roundtrip_diff(catalog: dict, base_url: str = "") -> diff.DiffResult:
"""export → bytes → import pipeline → diff against the same catalog."""
text = "".join(serialize.catalog_to_csv(catalog.values()))
text = "".join(serialize.catalog_to_csv(catalog.values(), base_url=base_url))
parsed = codec.parse_csv(text.encode("utf-8"))
products = validate.build_products(parsed)
return diff.compute_diff(catalog, products)
@@ -186,7 +191,9 @@ def _roundtrip_diff(catalog: dict) -> diff.DiffResult:
def test_inv12_roundtrip_is_noop_over_generated_catalogs():
for seed in range(200):
catalog = _gen_catalog(seed)
result = _roundtrip_diff(catalog)
# A fixed base_url so fetched images export an absolute hosted URL; the
# diff resolves it back by id -> the round-trip stays a no-op.
result = _roundtrip_diff(catalog, base_url="https://shop.test")
assert result.summary["adds"] == 0, f"seed {seed}: {result.summary}"
assert result.summary["updates"] == 0, f"seed {seed}: {result.summary}"
assert result.summary["errors"] == 0, f"seed {seed}: {result.summary}"