fix(products): conservative dialect detection — canonical-distinctive columns veto Shopify (SLICE-8 review)
ci / check (push) Has been cancelled
ci / check (pull_request) Has been cancelled

Addresses adversarial-review findings: a canonical file carrying a stray
Shopify-signature name (e.g. SEO Title) no longer misdetects as Shopify and
silently drops canonical Type / corrupts the weight unit (kg->g). A
canonical-distinctive column (Description/Variant Cost/Variant Weight/Google
Product Category/Variant Volume/Tax ID/Position) now vetoes detection, so
detection leans conservative (under-detection warns; over-detection corrupts).
Also closes the dual-named-column shadow (Body (HTML)+Description) and the
stale-weight-unit-on-clear case. Tests cover all three.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 02:36:09 -07:00
parent cb6b5996f3
commit a851d3587c
4 changed files with 87 additions and 11 deletions
@@ -76,3 +76,46 @@ def test_shopify_fixture_maps_exhaustively():
):
assert col in parsed.unknown_columns, col
assert "Variant Compare At Price" not in first
# --- Detection robustness (review findings #1, #2): bias conservative ----------------
def test_canonical_distinctive_column_vetoes_shopify_detection():
# A canonical file carrying a stray Shopify-signature name (`SEO Title`) stays
# canonical because it also has canonical-distinctive columns — no misparse,
# no dropped Type, no corrupted weight unit (review finding #2).
header = ["Handle", "Title", "Type", "Variant Weight", "Variant Weight Unit", "SEO Title"]
assert is_shopify_header(header) is False
def test_dual_named_file_stays_canonical_and_warns_shopify_name():
# A malformed file with BOTH `Body (HTML)` and canonical `Description`: the
# canonical column vetoes detection, so `Body (HTML)` is honestly warned as an
# unknown column rather than silently shadowing `Description` (review finding #1).
data = (
b"Handle,Title,Body (HTML),Description,Variant Price\n"
b"mug,Moon Mug,FROM_BODY,FROM_DESC,18.00\n"
)
parsed = parse_csv(data)
assert parsed.dialect == "canonical"
assert parsed.rows[0].cells["Description"] == "FROM_DESC"
assert "Body (HTML)" in parsed.unknown_columns
def test_real_shopify_export_still_detected():
# The conservative veto must not break a genuine Shopify export (no canonical-
# distinctive columns present).
parsed = parse_csv(_FIXTURE.read_bytes())
assert parsed.dialect == "shopify"
def test_shopify_grams_clear_clears_weight_unit_together():
# An empty Variant Grams (a clear) clears the synthesized unit too — weight and
# unit move together, never a stale unit (review finding #3).
data = b"Handle,Title,Body (HTML),Variant Grams\nmug,Moon Mug,<p>x</p>,\n"
parsed = parse_csv(data)
assert parsed.dialect == "shopify"
cells = parsed.rows[0].cells
assert cells["Variant Weight"] == ""
assert cells["Variant Weight Unit"] == ""