32 lines
3.2 KiB
Markdown
32 lines
3.2 KiB
Markdown
# 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` + `PricingService` gain `seedVariantPrice(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 as `setVariantPrice` (`currency`/`created_at`/`updated_at` via DDL defaults). Idempotent + parameterized.
|
|
- `CreateProductVariant` ctor += `pricing: PricingServicePort` (type-only import of pricing **public index** → R1-clean); after `variants.create`, best-effort `await 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`: hoist `const pricing = createPricingService(deps.pool)` above `registerCatalogRoutes` so it can be passed in; pricing routes + cart keep using it (unchanged).
|
|
|
|
## Files changed (source)
|
|
- `pricing/domain/ports.ts` — +`seedVariantPrice` on `PricingService` + `PricingRepository`.
|
|
- `pricing/application/pricing-service.ts` — +`seedVariantPrice` delegate.
|
|
- `pricing/infrastructure/pg-pricing-repository.ts` — +`seedVariantPrice` impl.
|
|
- `catalog/application/variant-use-cases.ts` — import `PricingServicePort`; ctor + execute seed (best-effort).
|
|
- `catalog/api/catalog.routes.ts` — `CatalogRoutesDeps += pricing: PricingServicePort`; ctor passes `deps.pricing`.
|
|
- `app/build-app.ts` — hoist `pricing` const; pass to `registerCatalogRoutes` deps.
|
|
|
|
## 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.ts` AC itest (skipIf !hasDb): POST `/products/:id/variants` → `pricing_variant_prices` row exists with `net=0/vat=general/currency=EUR`.
|
|
- Collateral (fake fakes updated to satisfy new port method): `pricing/tests/pricing-service.test.ts` repo() factory, `cart/tests/cart-service.test.ts` + `checkout/tests/checkout-service.test.ts` PricingServicePort fakes — added `seedVariantPrice: async () => undefined` no-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 on `security.routes.ts:14`→`log-broadcaster` is out of F-138 scope; git diff proves untouched.)
|
|
- `./scripts/verify.sh` → **VERIFY_OK** (pre-close).
|