create-ailk 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -20
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +34 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +117 -550
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +5 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +2 -2
- package/templates/apps/api/CLAUDE.md +102 -76
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +67 -20
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +10 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,877 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file deliverable-fulfillment.test.ts
|
|
3
|
-
* @description Unit tests for the deliverable-fulfillment service (#3961
|
|
4
|
-
* D3/D4/D5/D7).
|
|
5
|
-
*
|
|
6
|
-
* External dependency boundaries mocked:
|
|
7
|
-
* - @working-theory/database (via moduleNameMapper → prisma-client.js mock; prisma singleton via __mocks__/prisma.ts)
|
|
8
|
-
* - @working-theory/email sendDeliverableEmail
|
|
9
|
-
* - @working-theory/observability logger
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// eslint-disable-next-line no-restricted-syntax -- prisma is a DB boundary; __mocks__/prisma.ts activates the auto-mock (Jest requires explicit call for local files)
|
|
13
|
-
jest.mock("../../lib/prisma.js");
|
|
14
|
-
|
|
15
|
-
const mockSendDeliverableEmail = jest.fn();
|
|
16
|
-
jest.mock("@working-theory/email", () => ({
|
|
17
|
-
sendDeliverableEmail: (...args: unknown[]) =>
|
|
18
|
-
mockSendDeliverableEmail(...args),
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
const mockLoggerInfo = jest.fn();
|
|
22
|
-
const mockLoggerWarn = jest.fn();
|
|
23
|
-
const mockLoggerError = jest.fn();
|
|
24
|
-
jest.mock("@working-theory/observability", () => ({
|
|
25
|
-
logger: {
|
|
26
|
-
info: (...args: unknown[]) => mockLoggerInfo(...args),
|
|
27
|
-
warn: (...args: unknown[]) => mockLoggerWarn(...args),
|
|
28
|
-
error: (...args: unknown[]) => mockLoggerError(...args),
|
|
29
|
-
debug: jest.fn(),
|
|
30
|
-
},
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
import { prisma } from "../../lib/prisma.js";
|
|
34
|
-
import {
|
|
35
|
-
fulfillDeliverable,
|
|
36
|
-
listSendlessGrants,
|
|
37
|
-
type FulfillDeliverableInput,
|
|
38
|
-
} from "../deliverable-fulfillment";
|
|
39
|
-
|
|
40
|
-
// ─── Fixtures ─────────────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
const en = (s: string) => ({ en: s });
|
|
43
|
-
|
|
44
|
-
const SITE_ID = "site-abc";
|
|
45
|
-
const SESSION_ID = "flsess_00000000-0000-7000-8000-0000000000c1";
|
|
46
|
-
const LEAD_ID = "lead_00000000-0000-7000-8000-0000000000e1";
|
|
47
|
-
const GRANT_ID = "dgrant_00000000-0000-7000-8000-0000000000g1";
|
|
48
|
-
|
|
49
|
-
// #6257 — the seeded sentinel workspace the `"default"` alias resolves to
|
|
50
|
-
// (`@working-theory/database/tenancy`'s DEFAULT_WORKSPACE_ID), with a
|
|
51
|
-
// sentinel-shaped organizationId so `resolveSentinelWorkspaceRow`'s own
|
|
52
|
-
// `isSentinel` check passes. Used by ONE test — the `listSendlessGrants`
|
|
53
|
-
// resolution test, the only test in this file that exercises an alias at
|
|
54
|
-
// all — to prove that read scopes by the id it RESOLVED rather than by the
|
|
55
|
-
// alias string the CLI was given. The other fence tests below run no
|
|
56
|
-
// resolution: `fulfillDeliverable` is handed an already-resolved id.
|
|
57
|
-
const SENTINEL_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
|
|
58
|
-
const SENTINEL_ORGANIZATION_ID = "org_00000000-0000-7000-8000-000000000001";
|
|
59
|
-
|
|
60
|
-
function deliverablesBlock() {
|
|
61
|
-
return {
|
|
62
|
-
assets: [
|
|
63
|
-
{ segment: "small", assetKey: "guide-small" },
|
|
64
|
-
{ segment: "large", assetKey: "guide-large" },
|
|
65
|
-
{ assetKey: "guide-default" },
|
|
66
|
-
],
|
|
67
|
-
copy: {
|
|
68
|
-
subject: en("Here's your guide"),
|
|
69
|
-
body: en("Thanks — your download is attached."),
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function baseConfigSnapshot() {
|
|
75
|
-
return {
|
|
76
|
-
slug: "get-a-quote",
|
|
77
|
-
defaultLocale: "en",
|
|
78
|
-
steps: [],
|
|
79
|
-
deliverables: deliverablesBlock(),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function baseInput(
|
|
84
|
-
overrides: Partial<FulfillDeliverableInput> = {},
|
|
85
|
-
): FulfillDeliverableInput {
|
|
86
|
-
return {
|
|
87
|
-
siteId: SITE_ID,
|
|
88
|
-
flowSlug: "get-a-quote",
|
|
89
|
-
configSnapshot: baseConfigSnapshot(),
|
|
90
|
-
segment: "small",
|
|
91
|
-
leadId: LEAD_ID,
|
|
92
|
-
sessionId: SESSION_ID,
|
|
93
|
-
email: "lead@example.com",
|
|
94
|
-
locale: "en",
|
|
95
|
-
...overrides,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
describe("fulfillDeliverable", () => {
|
|
100
|
-
const originalEnv = process.env;
|
|
101
|
-
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
jest.clearAllMocks();
|
|
104
|
-
process.env = {
|
|
105
|
-
...originalEnv,
|
|
106
|
-
AILK_PUBLIC_API_URL: "https://api.example.com",
|
|
107
|
-
AILK_DELIVERABLE_EMAIL_FROM: "deliverable-default@example.com",
|
|
108
|
-
};
|
|
109
|
-
mockSendDeliverableEmail.mockResolvedValue({
|
|
110
|
-
ok: true,
|
|
111
|
-
messageId: "msg-test",
|
|
112
|
-
});
|
|
113
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue({
|
|
114
|
-
id: "dasset_00000000-0000-7000-8000-0000000000a1",
|
|
115
|
-
siteId: SITE_ID,
|
|
116
|
-
key: "guide-small",
|
|
117
|
-
});
|
|
118
|
-
(prisma.deliverableGrant.create as jest.Mock).mockResolvedValue({
|
|
119
|
-
id: GRANT_ID,
|
|
120
|
-
});
|
|
121
|
-
(prisma.deliverableGrant.update as jest.Mock).mockResolvedValue({
|
|
122
|
-
id: GRANT_ID,
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
afterEach(() => {
|
|
127
|
-
process.env = originalEnv;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// ── D3 — segment → entry resolution ──────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
it("resolves the segment-matched entry and mints a grant for its asset", async () => {
|
|
133
|
-
await fulfillDeliverable(baseInput({ segment: "small" }));
|
|
134
|
-
|
|
135
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
|
|
136
|
-
expect.objectContaining({
|
|
137
|
-
where: {
|
|
138
|
-
siteId_key: { siteId: SITE_ID, key: "guide-small" },
|
|
139
|
-
// #6257 — the legacy store's read is fenced by the same resolved
|
|
140
|
-
// workspace id the grant is minted under.
|
|
141
|
-
workspaceId: SITE_ID,
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("selects only the id on the legacy DeliverableAsset lookup, never the bytes blob (#9248)", async () => {
|
|
148
|
-
await fulfillDeliverable(baseInput({ segment: "small" }));
|
|
149
|
-
|
|
150
|
-
// `DeliverableAsset.bytes` is the file itself; this read exists only to
|
|
151
|
-
// learn the row's id, so the query must name that one column.
|
|
152
|
-
const [[args]] = (prisma.deliverableAsset.findUnique as jest.Mock).mock
|
|
153
|
-
.calls as [[{ select?: Record<string, unknown> }]];
|
|
154
|
-
expect(args.select).toEqual({ id: true });
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("resolves the OTHER segment's entry when segment differs (two-segment divergence, AC1)", async () => {
|
|
158
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue({
|
|
159
|
-
id: "dasset_00000000-0000-7000-8000-0000000000a2",
|
|
160
|
-
siteId: SITE_ID,
|
|
161
|
-
key: "guide-large",
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await fulfillDeliverable(baseInput({ segment: "large" }));
|
|
165
|
-
|
|
166
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
|
|
167
|
-
expect.objectContaining({
|
|
168
|
-
where: {
|
|
169
|
-
siteId_key: { siteId: SITE_ID, key: "guide-large" },
|
|
170
|
-
workspaceId: SITE_ID,
|
|
171
|
-
},
|
|
172
|
-
}),
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("falls back to the default entry when segment is undefined (N=1 degenerate case)", async () => {
|
|
177
|
-
await fulfillDeliverable(baseInput({ segment: undefined }));
|
|
178
|
-
|
|
179
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
|
|
180
|
-
expect.objectContaining({
|
|
181
|
-
where: {
|
|
182
|
-
siteId_key: { siteId: SITE_ID, key: "guide-default" },
|
|
183
|
-
workspaceId: SITE_ID,
|
|
184
|
-
},
|
|
185
|
-
}),
|
|
186
|
-
);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("falls back to the default entry when segment is derived but unmapped", async () => {
|
|
190
|
-
await fulfillDeliverable(baseInput({ segment: "unmapped-segment" }));
|
|
191
|
-
|
|
192
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
|
|
193
|
-
expect.objectContaining({
|
|
194
|
-
where: {
|
|
195
|
-
siteId_key: { siteId: SITE_ID, key: "guide-default" },
|
|
196
|
-
workspaceId: SITE_ID,
|
|
197
|
-
},
|
|
198
|
-
}),
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("skips (no grant, no send) when no entry matches and there is no default", async () => {
|
|
203
|
-
await fulfillDeliverable(
|
|
204
|
-
baseInput({
|
|
205
|
-
segment: "unmapped-segment",
|
|
206
|
-
configSnapshot: {
|
|
207
|
-
...baseConfigSnapshot(),
|
|
208
|
-
deliverables: {
|
|
209
|
-
assets: [{ segment: "small", assetKey: "guide-small" }],
|
|
210
|
-
copy: deliverablesBlock().copy,
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
217
|
-
// #6310 FR8: a no_entry resolution exits before EITHER store is read.
|
|
218
|
-
expect(prisma.flowDefinition.findUnique).not.toHaveBeenCalled();
|
|
219
|
-
expect(prisma.asset.findFirst).not.toHaveBeenCalled();
|
|
220
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
221
|
-
expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
|
|
222
|
-
// AC4: no_entry is a pre-mint outcome — no grant exists to stamp.
|
|
223
|
-
expect(prisma.deliverableGrant.update).not.toHaveBeenCalled();
|
|
224
|
-
expect(mockLoggerInfo).toHaveBeenCalledWith(
|
|
225
|
-
expect.objectContaining({
|
|
226
|
-
siteId: SITE_ID,
|
|
227
|
-
flowSlug: "get-a-quote",
|
|
228
|
-
sessionId: SESSION_ID,
|
|
229
|
-
}),
|
|
230
|
-
expect.any(String),
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// ── D3 — missing asset row ────────────────────────────────────────────────
|
|
235
|
-
|
|
236
|
-
it("skips (no grant, no send) when the config-referenced asset row is missing", async () => {
|
|
237
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
|
|
238
|
-
|
|
239
|
-
await fulfillDeliverable(baseInput());
|
|
240
|
-
|
|
241
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
242
|
-
expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
|
|
243
|
-
// AC4: asset_missing is a pre-mint outcome — no grant exists to stamp.
|
|
244
|
-
expect(prisma.deliverableGrant.update).not.toHaveBeenCalled();
|
|
245
|
-
expect(mockLoggerInfo).toHaveBeenCalled();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// ── FEAT-074 U4b (#4718) D4 — the playbook: virtual asset ────────────────
|
|
249
|
-
|
|
250
|
-
function playbookConfigSnapshot() {
|
|
251
|
-
return {
|
|
252
|
-
slug: "targeting-maturity",
|
|
253
|
-
defaultLocale: "en",
|
|
254
|
-
steps: [],
|
|
255
|
-
deliverables: {
|
|
256
|
-
assets: [{ assetKey: "playbook:targeting-maturity" }],
|
|
257
|
-
copy: {
|
|
258
|
-
subject: en("Your Playbook is ready"),
|
|
259
|
-
body: en("Your Playbook is ready to download."),
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
it("skips the DeliverableAsset lookup and mints the grant with assetId = entry.assetKey when a PlaybookInstance exists (AC-3)", async () => {
|
|
266
|
-
(prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue({
|
|
267
|
-
id: "pbk_00000000-0000-7000-8000-0000000000p1",
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
await fulfillDeliverable(
|
|
271
|
-
baseInput({ segment: undefined, configSnapshot: playbookConfigSnapshot() }),
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
expect(prisma.playbookInstance.findFirst).toHaveBeenCalledWith(
|
|
275
|
-
expect.objectContaining({
|
|
276
|
-
where: {
|
|
277
|
-
siteId: SITE_ID,
|
|
278
|
-
// #6257 — the playbook existence check is fenced too.
|
|
279
|
-
workspaceId: SITE_ID,
|
|
280
|
-
leadId: LEAD_ID,
|
|
281
|
-
experimentKey: "targeting-maturity",
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
284
|
-
);
|
|
285
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
286
|
-
// AC-3: EXACTLY one grant -- toHaveBeenCalledTimes, not merely
|
|
287
|
-
// toHaveBeenCalledWith (which would pass even on a second, unasserted
|
|
288
|
-
// call).
|
|
289
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalledTimes(1);
|
|
290
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
|
|
291
|
-
expect.objectContaining({
|
|
292
|
-
data: expect.objectContaining({
|
|
293
|
-
siteId: SITE_ID,
|
|
294
|
-
assetId: "playbook:targeting-maturity",
|
|
295
|
-
leadId: LEAD_ID,
|
|
296
|
-
sessionId: SESSION_ID,
|
|
297
|
-
}),
|
|
298
|
-
}),
|
|
299
|
-
);
|
|
300
|
-
// AC-3: the send-and-stamp tail reaches sendStatus = sent WITH the
|
|
301
|
-
// /v1/deliverables/<grantId> link -- asserted on the actual email call,
|
|
302
|
-
// not merely "an email was sent."
|
|
303
|
-
expect(mockSendDeliverableEmail).toHaveBeenCalledTimes(1);
|
|
304
|
-
expect(mockSendDeliverableEmail).toHaveBeenCalledWith(
|
|
305
|
-
expect.objectContaining({
|
|
306
|
-
downloadLink: `https://api.example.com/v1/deliverables/${GRANT_ID}`,
|
|
307
|
-
}),
|
|
308
|
-
);
|
|
309
|
-
expect(prisma.deliverableGrant.update).toHaveBeenCalledWith({
|
|
310
|
-
where: { id: GRANT_ID },
|
|
311
|
-
data: { sendStatus: "sent", sendAt: expect.any(Date) },
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("logs playbook_missing, mints no grant, and sends no email when the lead has no PlaybookInstance (AC-4)", async () => {
|
|
316
|
-
(prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue(null);
|
|
317
|
-
|
|
318
|
-
await fulfillDeliverable(
|
|
319
|
-
baseInput({ segment: undefined, configSnapshot: playbookConfigSnapshot() }),
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
323
|
-
expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
|
|
324
|
-
// playbook_missing is a pre-mint outcome — no grant exists to stamp
|
|
325
|
-
// (the same posture as asset_missing).
|
|
326
|
-
expect(prisma.deliverableGrant.update).not.toHaveBeenCalled();
|
|
327
|
-
expect(mockLoggerInfo).toHaveBeenCalledWith(
|
|
328
|
-
expect.objectContaining({
|
|
329
|
-
siteId: SITE_ID,
|
|
330
|
-
sessionId: SESSION_ID,
|
|
331
|
-
outcome: "playbook_missing",
|
|
332
|
-
}),
|
|
333
|
-
expect.any(String),
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("an ordinary (non-playbook:) assetKey never queries playbookInstance (no false-positive branch)", async () => {
|
|
338
|
-
await fulfillDeliverable(baseInput());
|
|
339
|
-
|
|
340
|
-
expect(prisma.playbookInstance.findFirst).not.toHaveBeenCalled();
|
|
341
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it("a playbook: deliverable issues zero Asset or FlowDefinition reads (#6310 FR8)", async () => {
|
|
345
|
-
(prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue({
|
|
346
|
-
id: "pbk_00000000-0000-7000-8000-0000000000p1",
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
await fulfillDeliverable(
|
|
350
|
-
baseInput({ segment: undefined, configSnapshot: playbookConfigSnapshot() }),
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
expect(prisma.flowDefinition.findUnique).not.toHaveBeenCalled();
|
|
354
|
-
expect(prisma.asset.findFirst).not.toHaveBeenCalled();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// ── #6310 D1/D2/D4/D5/D15 — Asset-first resolution ───────────────────────
|
|
358
|
-
|
|
359
|
-
const WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
|
|
360
|
-
const OTHER_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000002";
|
|
361
|
-
const NEW_ASSET_ID = "asset_00000000-0000-7000-8000-0000000000b1";
|
|
362
|
-
|
|
363
|
-
it("resolves against the workspace Asset store first, and mints the grant with the asset_ id AND workspaceId (D1/D2/D15)", async () => {
|
|
364
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
365
|
-
workspaceId: WORKSPACE_ID,
|
|
366
|
-
});
|
|
367
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID, tags: [] });
|
|
368
|
-
|
|
369
|
-
await fulfillDeliverable(baseInput());
|
|
370
|
-
|
|
371
|
-
expect(prisma.flowDefinition.findUnique).toHaveBeenCalledWith(
|
|
372
|
-
expect.objectContaining({
|
|
373
|
-
where: { siteId_slug: { siteId: SITE_ID, slug: "get-a-quote" } },
|
|
374
|
-
select: { workspaceId: true },
|
|
375
|
-
}),
|
|
376
|
-
);
|
|
377
|
-
expect(prisma.asset.findFirst).toHaveBeenCalledWith({
|
|
378
|
-
where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
|
|
379
|
-
select: { id: true, tags: true },
|
|
380
|
-
});
|
|
381
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
382
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
|
|
383
|
-
expect.objectContaining({
|
|
384
|
-
data: expect.objectContaining({
|
|
385
|
-
siteId: SITE_ID,
|
|
386
|
-
assetId: NEW_ASSET_ID,
|
|
387
|
-
workspaceId: WORKSPACE_ID,
|
|
388
|
-
leadId: LEAD_ID,
|
|
389
|
-
sessionId: SESSION_ID,
|
|
390
|
-
}),
|
|
391
|
-
}),
|
|
392
|
-
);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it("falls back to the legacy DeliverableAsset store on an Asset miss, and mints a grant with NO workspaceId (D2/D3)", async () => {
|
|
396
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
397
|
-
workspaceId: WORKSPACE_ID,
|
|
398
|
-
});
|
|
399
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
400
|
-
|
|
401
|
-
await fulfillDeliverable(baseInput());
|
|
402
|
-
|
|
403
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
|
|
404
|
-
expect.objectContaining({
|
|
405
|
-
where: {
|
|
406
|
-
siteId_key: { siteId: SITE_ID, key: "guide-small" },
|
|
407
|
-
// #6257 — the legacy store's read is fenced by the same resolved
|
|
408
|
-
// workspace id the grant is minted under.
|
|
409
|
-
workspaceId: SITE_ID,
|
|
410
|
-
},
|
|
411
|
-
}),
|
|
412
|
-
);
|
|
413
|
-
const [[createArgs]] = (prisma.deliverableGrant.create as jest.Mock).mock
|
|
414
|
-
.calls as [[{ data: Record<string, unknown> }]];
|
|
415
|
-
expect(createArgs.data["assetId"]).toBe(
|
|
416
|
-
"dasset_00000000-0000-7000-8000-0000000000a1",
|
|
417
|
-
);
|
|
418
|
-
// #6374 D4 / AC-8 — this mint site now ALWAYS stamps `workspaceId`,
|
|
419
|
-
// including on the legacy `dasset_` arm this test exercises, and the
|
|
420
|
-
// value is the grant's own `siteId`. NULL remains the sentinel for the
|
|
421
|
-
// rows that already EXIST (the migration touches none of them); it is no
|
|
422
|
-
// longer the sentinel for a row this code mints, because a mint always
|
|
423
|
-
// knows its workspace. The download route is unaffected: it branches on
|
|
424
|
-
// the `assetId` PREFIX and consults `workspaceId` only on the `asset_`
|
|
425
|
-
// arm.
|
|
426
|
-
expect(createArgs.data["workspaceId"]).toBe(createArgs.data["siteId"]);
|
|
427
|
-
expect(createArgs.data["workspaceId"]).toBe(SITE_ID);
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it("prefers the Asset match over an existing legacy DeliverableAsset row at the SAME key (D2 order)", async () => {
|
|
431
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
432
|
-
workspaceId: WORKSPACE_ID,
|
|
433
|
-
});
|
|
434
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID, tags: [] });
|
|
435
|
-
// beforeEach's default deliverableAsset.findUnique mock already resolves
|
|
436
|
-
// a row at this key — it must never even be consulted.
|
|
437
|
-
|
|
438
|
-
await fulfillDeliverable(baseInput());
|
|
439
|
-
|
|
440
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
441
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
|
|
442
|
-
expect.objectContaining({
|
|
443
|
-
data: expect.objectContaining({ assetId: NEW_ASSET_ID }),
|
|
444
|
-
}),
|
|
445
|
-
);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it("skips the Asset lookup entirely when the definition's workspaceId is null — never guesses one (D5)", async () => {
|
|
449
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
450
|
-
workspaceId: null,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
await fulfillDeliverable(baseInput());
|
|
454
|
-
|
|
455
|
-
expect(prisma.asset.findFirst).not.toHaveBeenCalled();
|
|
456
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it("skips the Asset lookup entirely when no FlowDefinition row resolves (D5)", async () => {
|
|
460
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue(null);
|
|
461
|
-
|
|
462
|
-
await fulfillDeliverable(baseInput());
|
|
463
|
-
|
|
464
|
-
expect(prisma.asset.findFirst).not.toHaveBeenCalled();
|
|
465
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it("mints no grant and logs asset_missing when NEITHER store resolves the key", async () => {
|
|
469
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
470
|
-
workspaceId: WORKSPACE_ID,
|
|
471
|
-
});
|
|
472
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
473
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
|
|
474
|
-
|
|
475
|
-
await fulfillDeliverable(baseInput());
|
|
476
|
-
|
|
477
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
478
|
-
expect(mockLoggerInfo).toHaveBeenCalledWith(
|
|
479
|
-
expect.objectContaining({ outcome: "asset_missing" }),
|
|
480
|
-
expect.any(String),
|
|
481
|
-
);
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// ── #8848 D3 — the minted grant id, and a deliverable flow's Asset-only store ──
|
|
485
|
-
|
|
486
|
-
it("resolves { grantId } equal to the created row's id on a successful mint", async () => {
|
|
487
|
-
await expect(fulfillDeliverable(baseInput())).resolves.toEqual({ grantId: GRANT_ID });
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it("resolves undefined on the asset_missing path", async () => {
|
|
491
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({ workspaceId: WORKSPACE_ID });
|
|
492
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
493
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
|
|
494
|
-
|
|
495
|
-
await expect(fulfillDeliverable(baseInput())).resolves.toBeUndefined();
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it("a gated file snapshot whose key names only a legacy DeliverableAsset mints no grant (SEC-8848-B)", async () => {
|
|
499
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({ workspaceId: WORKSPACE_ID });
|
|
500
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
501
|
-
const configSnapshot = {
|
|
502
|
-
...baseConfigSnapshot(),
|
|
503
|
-
deliverables: { ...deliverablesBlock(), assets: [{ assetKey: "listing-acme-2026-10-12" }] },
|
|
504
|
-
deliverable: { kind: "file", gated: true, title: en("Q3"), geometry: "letter", pages: 3 },
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
await expect(
|
|
508
|
-
fulfillDeliverable(baseInput({ segment: undefined, configSnapshot })),
|
|
509
|
-
).resolves.toBeUndefined();
|
|
510
|
-
|
|
511
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
512
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
513
|
-
expect(mockLoggerInfo).toHaveBeenCalledWith(
|
|
514
|
-
expect.objectContaining({ outcome: "asset_missing" }),
|
|
515
|
-
expect.any(String),
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it("the same legacy-only key on a flow with no deliverable block still mints (NFR1)", async () => {
|
|
520
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({ workspaceId: WORKSPACE_ID });
|
|
521
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
522
|
-
const configSnapshot = {
|
|
523
|
-
...baseConfigSnapshot(),
|
|
524
|
-
deliverables: { ...deliverablesBlock(), assets: [{ assetKey: "listing-acme-2026-10-12" }] },
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
await expect(
|
|
528
|
-
fulfillDeliverable(baseInput({ segment: undefined, configSnapshot })),
|
|
529
|
-
).resolves.toEqual({ grantId: GRANT_ID });
|
|
530
|
-
expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it("never reaches a key that exists only in ANOTHER workspace (NFR3) — the lookup is scoped to this flow's own workspaceId alone", async () => {
|
|
534
|
-
(prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
|
|
535
|
-
workspaceId: WORKSPACE_ID,
|
|
536
|
-
});
|
|
537
|
-
(prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
|
|
538
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
|
|
539
|
-
|
|
540
|
-
await fulfillDeliverable(baseInput());
|
|
541
|
-
|
|
542
|
-
expect(prisma.asset.findFirst).toHaveBeenCalledWith({
|
|
543
|
-
where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
|
|
544
|
-
select: { id: true, tags: true },
|
|
545
|
-
});
|
|
546
|
-
expect(prisma.asset.findFirst).not.toHaveBeenCalledWith(
|
|
547
|
-
expect.objectContaining({
|
|
548
|
-
where: expect.objectContaining({ workspaceId: OTHER_WORKSPACE_ID }),
|
|
549
|
-
}),
|
|
550
|
-
);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// ── D5 — grant mint ───────────────────────────────────────────────────────
|
|
554
|
-
|
|
555
|
-
it("mints the grant with assetId/leadId/sessionId", async () => {
|
|
556
|
-
await fulfillDeliverable(baseInput());
|
|
557
|
-
|
|
558
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
|
|
559
|
-
expect.objectContaining({
|
|
560
|
-
data: expect.objectContaining({
|
|
561
|
-
siteId: SITE_ID,
|
|
562
|
-
assetId: "dasset_00000000-0000-7000-8000-0000000000a1",
|
|
563
|
-
leadId: LEAD_ID,
|
|
564
|
-
sessionId: SESSION_ID,
|
|
565
|
-
}),
|
|
566
|
-
}),
|
|
567
|
-
);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// ── AC5 — mint-before-send is preserved (regression floor) ──────────────
|
|
571
|
-
|
|
572
|
-
it("still calls deliverableGrant.create before sendDeliverableEmail (AC5)", async () => {
|
|
573
|
-
await fulfillDeliverable(baseInput());
|
|
574
|
-
|
|
575
|
-
const createOrder = (prisma.deliverableGrant.create as jest.Mock).mock
|
|
576
|
-
.invocationCallOrder[0];
|
|
577
|
-
const sendOrder = mockSendDeliverableEmail.mock.invocationCallOrder[0];
|
|
578
|
-
expect(createOrder).toBeLessThan(sendOrder);
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// ── AC4 — the three post-mint outcomes all stamp ─────────────────────────
|
|
582
|
-
|
|
583
|
-
it("stamps sendStatus: 'sent' + a non-null sendAt on a successful send", async () => {
|
|
584
|
-
await fulfillDeliverable(baseInput());
|
|
585
|
-
|
|
586
|
-
expect(prisma.deliverableGrant.update).toHaveBeenCalledWith({
|
|
587
|
-
where: { id: GRANT_ID },
|
|
588
|
-
data: { sendStatus: "sent", sendAt: expect.any(Date) },
|
|
589
|
-
});
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// ── AC2 — link = base + grant id, no PII ────────────────────────────────
|
|
593
|
-
|
|
594
|
-
it("builds the download link from AILK_PUBLIC_API_URL + the grant id, with no PII", async () => {
|
|
595
|
-
await fulfillDeliverable(baseInput({ email: "lead-with-pii@example.com" }));
|
|
596
|
-
|
|
597
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
598
|
-
expect(call[0].downloadLink).toBe(
|
|
599
|
-
`https://api.example.com/v1/deliverables/${GRANT_ID}`,
|
|
600
|
-
);
|
|
601
|
-
expect(call[0].downloadLink).not.toContain("lead-with-pii@example.com");
|
|
602
|
-
expect(call[0].downloadLink).not.toContain(LEAD_ID);
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
it("sends to the lead's email (recipient)", async () => {
|
|
606
|
-
await fulfillDeliverable(baseInput());
|
|
607
|
-
|
|
608
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
609
|
-
expect(call[0].to).toBe("lead@example.com");
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
// ── D7 — copy resolution at locale, with defaultLocale fallback ────────────
|
|
613
|
-
|
|
614
|
-
it("resolves copy at the completion request's locale", async () => {
|
|
615
|
-
const configSnapshot = {
|
|
616
|
-
...baseConfigSnapshot(),
|
|
617
|
-
deliverables: {
|
|
618
|
-
assets: [{ assetKey: "guide-default" }],
|
|
619
|
-
copy: {
|
|
620
|
-
subject: { en: "Here's your guide", "pt-BR": "Aqui está seu guia" },
|
|
621
|
-
body: {
|
|
622
|
-
en: "Thanks — your download is attached.",
|
|
623
|
-
"pt-BR": "Obrigado — seu download está anexado.",
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
await fulfillDeliverable(
|
|
630
|
-
baseInput({ configSnapshot, locale: "pt-BR", segment: undefined }),
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
634
|
-
expect(call[0].subject).toBe("Aqui está seu guia");
|
|
635
|
-
expect(call[0].body).toBe("Obrigado — seu download está anexado.");
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
it("falls back to defaultLocale when the completion locale has no copy key", async () => {
|
|
639
|
-
const configSnapshot = {
|
|
640
|
-
...baseConfigSnapshot(),
|
|
641
|
-
deliverables: {
|
|
642
|
-
assets: [{ assetKey: "guide-default" }],
|
|
643
|
-
copy: {
|
|
644
|
-
subject: { en: "Here's your guide" },
|
|
645
|
-
body: { en: "Thanks — your download is attached." },
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
await fulfillDeliverable(
|
|
651
|
-
baseInput({ configSnapshot, locale: "fr", segment: undefined }),
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
655
|
-
expect(call[0].subject).toBe("Here's your guide");
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
it("resolves cta when present", async () => {
|
|
659
|
-
const configSnapshot = {
|
|
660
|
-
...baseConfigSnapshot(),
|
|
661
|
-
deliverables: {
|
|
662
|
-
assets: [{ assetKey: "guide-default" }],
|
|
663
|
-
copy: {
|
|
664
|
-
subject: en("Here's your guide"),
|
|
665
|
-
body: en("Thanks — your download is attached."),
|
|
666
|
-
cta: en("Download now"),
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
await fulfillDeliverable(baseInput({ configSnapshot, segment: undefined }));
|
|
672
|
-
|
|
673
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
674
|
-
expect(call[0].cta).toBe("Download now");
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
// ── sender-env fallback chain ────────────────────────────────────────────
|
|
678
|
-
|
|
679
|
-
it("uses AILK_DELIVERABLE_EMAIL_FROM when set", async () => {
|
|
680
|
-
process.env["AILK_DELIVERABLE_EMAIL_FROM"] = "deliverable@example.com";
|
|
681
|
-
|
|
682
|
-
await fulfillDeliverable(baseInput());
|
|
683
|
-
|
|
684
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
685
|
-
expect(call[0].fromAddress).toBe("deliverable@example.com");
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
it("falls back to AILK_LEAD_NOTIFICATION_FROM when AILK_DELIVERABLE_EMAIL_FROM is unset", async () => {
|
|
689
|
-
delete process.env["AILK_DELIVERABLE_EMAIL_FROM"];
|
|
690
|
-
process.env["AILK_LEAD_NOTIFICATION_FROM"] = "owner-noreply@example.com";
|
|
691
|
-
|
|
692
|
-
await fulfillDeliverable(baseInput());
|
|
693
|
-
|
|
694
|
-
const [call] = mockSendDeliverableEmail.mock.calls;
|
|
695
|
-
expect(call[0].fromAddress).toBe("owner-noreply@example.com");
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
it("skips the send (grant NOT wasted) when neither from-address env is set", async () => {
|
|
699
|
-
delete process.env["AILK_DELIVERABLE_EMAIL_FROM"];
|
|
700
|
-
delete process.env["AILK_LEAD_NOTIFICATION_FROM"];
|
|
701
|
-
|
|
702
|
-
await fulfillDeliverable(baseInput());
|
|
703
|
-
|
|
704
|
-
expect(prisma.deliverableGrant.create).toHaveBeenCalled();
|
|
705
|
-
expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
|
|
706
|
-
expect(mockLoggerInfo).toHaveBeenCalled();
|
|
707
|
-
// AC4: sender_unconfigured is a post-mint outcome — it stamps too.
|
|
708
|
-
expect(prisma.deliverableGrant.update).toHaveBeenCalledWith({
|
|
709
|
-
where: { id: GRANT_ID },
|
|
710
|
-
data: { sendStatus: "sender_unconfigured", sendAt: expect.any(Date) },
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// ── D4 — no deliverables block: opt-in per flow ─────────────────────────
|
|
715
|
-
|
|
716
|
-
it("does nothing when configSnapshot.deliverables is absent (opt-in per flow)", async () => {
|
|
717
|
-
await fulfillDeliverable(
|
|
718
|
-
baseInput({
|
|
719
|
-
configSnapshot: { ...baseConfigSnapshot(), deliverables: undefined },
|
|
720
|
-
}),
|
|
721
|
-
);
|
|
722
|
-
|
|
723
|
-
expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
|
|
724
|
-
expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
|
|
725
|
-
expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
// ── PII-safe logging ─────────────────────────────────────────────────────
|
|
729
|
-
|
|
730
|
-
it("never logs email or copy values", async () => {
|
|
731
|
-
delete process.env["AILK_DELIVERABLE_EMAIL_FROM"];
|
|
732
|
-
delete process.env["AILK_LEAD_NOTIFICATION_FROM"];
|
|
733
|
-
|
|
734
|
-
await fulfillDeliverable(baseInput({ email: "sentinel-pii@example.com" }));
|
|
735
|
-
|
|
736
|
-
const allLogCalls = [
|
|
737
|
-
...mockLoggerInfo.mock.calls,
|
|
738
|
-
...mockLoggerWarn.mock.calls,
|
|
739
|
-
...mockLoggerError.mock.calls,
|
|
740
|
-
];
|
|
741
|
-
for (const call of allLogCalls) {
|
|
742
|
-
expect(JSON.stringify(call)).not.toContain("sentinel-pii@example.com");
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
// ── Email send failure — logged, not thrown ──────────────────────────────
|
|
747
|
-
|
|
748
|
-
it("logs (does not throw) when the email send fails", async () => {
|
|
749
|
-
mockSendDeliverableEmail.mockResolvedValue({
|
|
750
|
-
ok: false,
|
|
751
|
-
error: { kind: "send_failed", message: "Resend down" },
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
await expect(fulfillDeliverable(baseInput())).resolves.toEqual({ grantId: GRANT_ID });
|
|
755
|
-
expect(mockLoggerInfo).toHaveBeenCalled();
|
|
756
|
-
// AC4: send_failed is a post-mint outcome — it stamps too.
|
|
757
|
-
expect(prisma.deliverableGrant.update).toHaveBeenCalledWith({
|
|
758
|
-
where: { id: GRANT_ID },
|
|
759
|
-
data: { sendStatus: "send_failed", sendAt: expect.any(Date) },
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
// ── #3998 D5 — a stamp failure is caught, never escalated ────────────────
|
|
764
|
-
|
|
765
|
-
it("does not throw when the stamp write itself rejects", async () => {
|
|
766
|
-
(prisma.deliverableGrant.update as jest.Mock).mockRejectedValue(
|
|
767
|
-
new Error("connection reset"),
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
await expect(fulfillDeliverable(baseInput())).resolves.toEqual({ grantId: GRANT_ID });
|
|
771
|
-
expect(mockSendDeliverableEmail).toHaveBeenCalled();
|
|
772
|
-
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
|
773
|
-
expect.objectContaining({ grantId: GRANT_ID, outcome: "sent" }),
|
|
774
|
-
expect.any(String),
|
|
775
|
-
);
|
|
776
|
-
});
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
// ─── #6257 — the fence: every fenced read carries workspaceId ───────────────
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
* Unit C2's contract for this service (#6257 AC-1/AC-2). Three of its reads
|
|
783
|
-
* are workspace-scopable and are scoped here; the rest are keyed on the grant
|
|
784
|
-
* id itself — the capability token — and carry a global-key marker comment
|
|
785
|
-
* at the call site instead.
|
|
786
|
-
*
|
|
787
|
-
* `fulfillDeliverable`'s `siteId` is the session's own, resolved before it
|
|
788
|
-
* reaches this service (#6254/#6374 AC-7); `listSendlessGrants` takes an
|
|
789
|
-
* OPERATOR-SUPPLIED alias off the `deliverable:resend` CLI, so it resolves
|
|
790
|
-
* that alias itself and fails closed when it names no workspace.
|
|
791
|
-
*/
|
|
792
|
-
describe("#6257 — every fenced read carries workspaceId", () => {
|
|
793
|
-
const originalEnv = process.env;
|
|
794
|
-
|
|
795
|
-
beforeEach(() => {
|
|
796
|
-
jest.clearAllMocks();
|
|
797
|
-
process.env = {
|
|
798
|
-
...originalEnv,
|
|
799
|
-
AILK_PUBLIC_API_URL: "https://api.example.com",
|
|
800
|
-
AILK_DELIVERABLE_EMAIL_FROM: "deliverable-default@example.com",
|
|
801
|
-
};
|
|
802
|
-
mockSendDeliverableEmail.mockResolvedValue({ ok: true, messageId: "msg-test" });
|
|
803
|
-
(prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue({
|
|
804
|
-
id: "dasset_00000000-0000-7000-8000-0000000000a1",
|
|
805
|
-
siteId: SITE_ID,
|
|
806
|
-
key: "guide-small",
|
|
807
|
-
});
|
|
808
|
-
(prisma.deliverableGrant.create as jest.Mock).mockResolvedValue({ id: GRANT_ID });
|
|
809
|
-
(prisma.deliverableGrant.update as jest.Mock).mockResolvedValue({ id: GRANT_ID });
|
|
810
|
-
(prisma.deliverableGrant.findMany as jest.Mock).mockResolvedValue([]);
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
afterEach(() => {
|
|
814
|
-
process.env = originalEnv;
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
it("scopes the LEGACY DeliverableAsset lookup by the session's workspace id", async () => {
|
|
818
|
-
await fulfillDeliverable(baseInput());
|
|
819
|
-
|
|
820
|
-
const { where } = (prisma.deliverableAsset.findUnique as jest.Mock).mock.calls[0][0];
|
|
821
|
-
expect(where.workspaceId).toBe(SITE_ID);
|
|
822
|
-
expect(where.siteId_key).toEqual({ siteId: SITE_ID, key: "guide-small" });
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
it("scopes the PlaybookInstance existence check by the session's workspace id", async () => {
|
|
826
|
-
(prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue({ id: "pbk_1" });
|
|
827
|
-
|
|
828
|
-
await fulfillDeliverable(
|
|
829
|
-
baseInput({
|
|
830
|
-
segment: undefined,
|
|
831
|
-
configSnapshot: {
|
|
832
|
-
slug: "get-a-quote",
|
|
833
|
-
defaultLocale: "en",
|
|
834
|
-
steps: [],
|
|
835
|
-
deliverables: {
|
|
836
|
-
assets: [{ assetKey: "playbook:targeting-maturity" }],
|
|
837
|
-
copy: { subject: en("x"), body: en("y") },
|
|
838
|
-
},
|
|
839
|
-
},
|
|
840
|
-
}),
|
|
841
|
-
);
|
|
842
|
-
|
|
843
|
-
expect(
|
|
844
|
-
(prisma.playbookInstance.findFirst as jest.Mock).mock.calls[0][0].where.workspaceId,
|
|
845
|
-
).toBe(SITE_ID);
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
it("listSendlessGrants resolves the CLI alias and scopes the list by the RESOLVED workspace id", async () => {
|
|
849
|
-
// The `"default"` alias resolves to the seeded sentinel workspace — the
|
|
850
|
-
// list must be scoped by THAT id, never by the alias string the operator
|
|
851
|
-
// typed on the command line. Passing the alias and asserting the SENTINEL
|
|
852
|
-
// id back is what discriminates a resolution from a passthrough: a test
|
|
853
|
-
// that passed the workspace id as the alias would assert the same string
|
|
854
|
-
// either way and could not fail (security review, round 1).
|
|
855
|
-
(prisma.workspace.findUnique as jest.Mock).mockImplementation(
|
|
856
|
-
async ({ where }: { where: { id: string } }) =>
|
|
857
|
-
where.id === SENTINEL_WORKSPACE_ID
|
|
858
|
-
? { id: SENTINEL_WORKSPACE_ID, organizationId: SENTINEL_ORGANIZATION_ID }
|
|
859
|
-
: null,
|
|
860
|
-
);
|
|
861
|
-
|
|
862
|
-
await listSendlessGrants({ siteId: "default" });
|
|
863
|
-
|
|
864
|
-
const { where } = (prisma.deliverableGrant.findMany as jest.Mock).mock.calls[0][0];
|
|
865
|
-
expect(where.workspaceId).toBe(SENTINEL_WORKSPACE_ID);
|
|
866
|
-
expect(where.siteId).toBe(SENTINEL_WORKSPACE_ID);
|
|
867
|
-
expect(where.workspaceId).not.toBe("default");
|
|
868
|
-
expect(where.sendStatus).toBeNull();
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
it("listSendlessGrants fails closed on an alias that resolves to no workspace", async () => {
|
|
872
|
-
(prisma.workspace.findUnique as jest.Mock).mockResolvedValue(null);
|
|
873
|
-
|
|
874
|
-
await expect(listSendlessGrants({ siteId: "no-such-site" })).resolves.toEqual([]);
|
|
875
|
-
expect(prisma.deliverableGrant.findMany).not.toHaveBeenCalled();
|
|
876
|
-
});
|
|
877
|
-
});
|