3.2 KiB
3.2 KiB
F-138 — Implementer evidence
What
Closed the GET /pricing/variants/:id 404 race: PricingService.seedVariantPrice (pricing owns its table) is injected into CreateProductVariant and called best-effort right after variant creation. The single constructor covers all 3 call sites (product autovariant, lazy variant GET, explicit POST variant).
Design recap (architect-approved)
PricingRepository+PricingServicegainseedVariantPrice(variantId): Promise<void>.PgPricingRepository.seedVariantPrice:INSERT pricing_variant_prices(variant_id, net_unit_amount_cents, offer_cents, cost_cents, vat_rate) VALUES ($1, 0, NULL, NULL, 'general') ON CONFLICT (variant_id) DO NOTHING— same column set assetVariantPrice(currency/created_at/updated_atvia DDL defaults). Idempotent + parameterized.CreateProductVariantctor +=pricing: PricingServicePort(type-only import of pricing public index → R1-clean); aftervariants.create, best-effortawait this.pricing.seedVariantPrice(variant.id)(try/catch: variant already persisted; seed failure must not fail/roll back variant creation).catalog.routes.ts:CatalogRoutesDeps += pricing;new CreateProductVariant(repository, variants, deps.pricing).build-app.ts: hoistconst pricing = createPricingService(deps.pool)aboveregisterCatalogRoutesso it can be passed in; pricing routes + cart keep using it (unchanged).
Files changed (source)
pricing/domain/ports.ts— +seedVariantPriceonPricingService+PricingRepository.pricing/application/pricing-service.ts— +seedVariantPricedelegate.pricing/infrastructure/pg-pricing-repository.ts— +seedVariantPriceimpl.catalog/application/variant-use-cases.ts— importPricingServicePort; ctor + execute seed (best-effort).catalog/api/catalog.routes.ts—CatalogRoutesDeps += pricing: PricingServicePort; ctor passesdeps.pricing.app/build-app.ts— hoistpricingconst; pass toregisterCatalogRoutesdeps.
Tests
- NEW
catalog/tests/variant-use-cases.test.ts(3 tests, runnable no DB): FakeProductRepository + FakeProductVariantRepository + FakePricingService — asserts seed-called-with-variant.id, no-seed-when-product-missing, seed-failure-doesn't-break-creation. catalog.itest.tsAC itest (skipIf !hasDb): POST/products/:id/variants→pricing_variant_pricesrow exists withnet=0/vat=general/currency=EUR.- Collateral (fake fakes updated to satisfy new port method):
pricing/tests/pricing-service.test.tsrepo() factory,cart/tests/cart-service.test.ts+checkout/tests/checkout-service.test.tsPricingServicePort fakes — addedseedVariantPrice: async () => undefinedno-op.
Verification (fresh run)
npx tsc --noEmit→ 0 errors (strict, noUncheckedIndexedAccess).npx vitest run(targeted: variant/pricing/cart/checkout/product/build-app) → 16/16 pass.npm test→ 209 passed | 57 skipped (was 206 → +3 new; no regression in cart/checkout/pricing/build-app).scripts/check-module-boundaries.mjs src→ 0 NEW R1 violations. (Pre-existing R1 onsecurity.routes.ts:14→log-broadcasteris out of F-138 scope; git diff proves untouched.)./scripts/verify.sh→ VERIFY_OK (pre-close).