docs(products): DOC-1 export ops + TEL-3, DOC-4 serializer/round-trip (SLICE-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ the spec is SD-0002 in the content repo.
|
||||
- `codec.py` — bytes → `ParsedFile`; file-level gates only.
|
||||
- `validate.py` — rows → `CanonicalProduct` blocks + per-row errors.
|
||||
- `diff.py` — catalog × canonical products → apply plan + preview records.
|
||||
- `serialize.py` — the export half: `CatalogProduct` snapshot → canonical CSV
|
||||
(the inverse of `codec`/`validate`). DB-free, like `diff.py`.
|
||||
- `repo.py` — SQL only: catalog snapshot, draft/run CRUD, apply primitives.
|
||||
Never commits or rolls back.
|
||||
- `service.py` — use-case orchestration; owns every transaction boundary and
|
||||
@@ -85,6 +87,31 @@ mismatch at confirm means the catalog drifted since preview → `PreviewStale`
|
||||
product/variant/image writes, error rows, draft delete — is one transaction;
|
||||
any exception rolls it back and emits TEL-6.
|
||||
|
||||
## Export & the round-trip (SLICE-6)
|
||||
|
||||
`serialize.py` is the export half of "one codec, two directions": it turns the
|
||||
`CatalogProduct` snapshot (the same one `repo.load_catalog` builds for the diff
|
||||
engine) back into canonical CSV, writing exactly the columns `codec.py` /
|
||||
`validate.py` parse, in the §6.5.1 row grammar — `HEADER` is the full canonical
|
||||
column set; product fields + option names sit on the first row; one variant per
|
||||
row; images interleave; a product with more images than variants emits
|
||||
image-only rows.
|
||||
|
||||
`repo.export_catalog` returns the status-filtered snapshot list (sorted by
|
||||
handle, deterministic); the `service.export_catalog` generator streams
|
||||
`serialize.catalog_to_csv` over it and emits TEL-3 (`catalog_exported`) once the
|
||||
stream is exhausted. The BFF wraps it in a `StreamingResponse`; an empty
|
||||
(filtered) catalog raises `EmptyCatalog` **eagerly** → `409 empty_catalog`
|
||||
before any bytes stream.
|
||||
|
||||
**INV-12** (`diff(catalog, import(export(catalog))) = ∅`) is locked two ways: a
|
||||
property test (`test_products_serialize.py`) runs the real
|
||||
export→parse→diff loop over 200 generated **text-field** catalogs and asserts
|
||||
every product is `unchanged`; the `e2e_roundtrip_noop` browser scenario does the
|
||||
same through the UI (export download → re-upload → all-unchanged preview, import
|
||||
disabled). Numeric/`Decimal` fields — the property test's deliberate blind spot
|
||||
(string-form vs value-identity) — get explicit round-trip unit tests.
|
||||
|
||||
## Named seams (what later slices replace)
|
||||
|
||||
- **`import_draft.file_bytes` → objectstore key (SLICE-7).** The upload
|
||||
@@ -107,6 +134,8 @@ any exception rolls it back and emits TEL-6.
|
||||
| `backend/tests/test_products_codec.py` | file-level gates: parse, caps (INV-18), required columns, dialect |
|
||||
| `backend/tests/test_products_validate.py` | every §6.5.1 row-error rule, one fixture each |
|
||||
| `backend/tests/test_products_diff.py` | classification, blank-vs-absent, option matching, fingerprint |
|
||||
| `backend/tests/test_products_serialize.py` | serializer grammar + INV-12 property test (round-trip no-op over generated catalogs) + decimal round-trip |
|
||||
| `backend/tests/test_products_export.py` | status-filtered snapshot, streamed export, EmptyCatalog, TEL-3 |
|
||||
| `backend/tests/test_products_service.py` | draft lifecycle: validate/preview/discard, expiry, TEL-1 |
|
||||
| `backend/tests/test_products_invariants.py` | INV-10 (never deletes), INV-14 (storefront isolation), apply transactionality, TEL-6 |
|
||||
| `backend/tests/test_products_endpoints.py` | §6.4 API scenarios + auth/storefront gates |
|
||||
@@ -114,3 +143,5 @@ any exception rolls it back and emits TEL-6.
|
||||
| `e2e/tests/import-errors.spec.ts` | actionable row errors at preview and on the run report |
|
||||
| `e2e/tests/import-file-rejected.spec.ts` | file-level rejection, picker stays live, no trace |
|
||||
| `e2e/tests/import-cancel.spec.ts` | cancel at preview leaves no trace |
|
||||
| `e2e/tests/export-download.spec.ts` | export downloads canonical CSV, status filter respected |
|
||||
| `e2e/tests/roundtrip-noop.spec.ts` | export → re-import → all-unchanged, import disabled (PUC-10) |
|
||||
|
||||
Reference in New Issue
Block a user