@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0
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/package.json +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mapWithConcurrency } from "./map-with-concurrency";
|
|
3
|
+
|
|
4
|
+
describe("mapWithConcurrency", () => {
|
|
5
|
+
test("preserves result order regardless of completion order", async () => {
|
|
6
|
+
const delays = [30, 10, 20, 0];
|
|
7
|
+
const results = await mapWithConcurrency(delays, 4, async (delay, index) => {
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
9
|
+
return index;
|
|
10
|
+
});
|
|
11
|
+
expect(results).toEqual([0, 1, 2, 3]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("never runs more than `limit` calls concurrently", async () => {
|
|
15
|
+
let active = 0;
|
|
16
|
+
let maxActive = 0;
|
|
17
|
+
const items = Array.from({ length: 12 }, (_, i) => i);
|
|
18
|
+
await mapWithConcurrency(items, 4, async (item) => {
|
|
19
|
+
active++;
|
|
20
|
+
maxActive = Math.max(maxActive, active);
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
22
|
+
active--;
|
|
23
|
+
return item;
|
|
24
|
+
});
|
|
25
|
+
expect(maxActive).toBeLessThanOrEqual(4);
|
|
26
|
+
expect(maxActive).toBeGreaterThan(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("propagates the first rejection", async () => {
|
|
30
|
+
const items = [1, 2, 3];
|
|
31
|
+
await expect(
|
|
32
|
+
mapWithConcurrency(items, 4, async (item) => {
|
|
33
|
+
if (item === 2) throw new Error("boom");
|
|
34
|
+
return item;
|
|
35
|
+
}),
|
|
36
|
+
).rejects.toThrow("boom");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Two workers rejecting at different times, under 4-way concurrency —
|
|
40
|
+
// regression guard for the #1398 hazard (an eagerly-fired, never-awaited
|
|
41
|
+
// Promise surfacing as an unattributed unhandled rejection). Every
|
|
42
|
+
// worker promise here is an element of the internal `Promise.all` array,
|
|
43
|
+
// so both rejections are always handled even though the aggregate
|
|
44
|
+
// `mapWithConcurrency` call already settled on the first one.
|
|
45
|
+
test("a second rejection after the first doesn't surface as an unhandled rejection", async () => {
|
|
46
|
+
const unhandled: unknown[] = [];
|
|
47
|
+
const onUnhandledRejection = (reason: unknown): void => {
|
|
48
|
+
unhandled.push(reason);
|
|
49
|
+
};
|
|
50
|
+
process.on("unhandledRejection", onUnhandledRejection);
|
|
51
|
+
try {
|
|
52
|
+
const items = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
53
|
+
await expect(
|
|
54
|
+
mapWithConcurrency(items, 4, async (item) => {
|
|
55
|
+
if (item === 2) {
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
57
|
+
throw new Error("boom-early");
|
|
58
|
+
}
|
|
59
|
+
if (item === 5) {
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
61
|
+
throw new Error("boom-late");
|
|
62
|
+
}
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
64
|
+
return item;
|
|
65
|
+
}),
|
|
66
|
+
).rejects.toThrow("boom-early");
|
|
67
|
+
// Give the later-rejecting worker's promise a chance to settle and
|
|
68
|
+
// for a real unhandled rejection (if any) to fire.
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
70
|
+
} finally {
|
|
71
|
+
process.off("unhandledRejection", onUnhandledRejection);
|
|
72
|
+
}
|
|
73
|
+
expect(unhandled).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("does not claim new items after the first rejection", async () => {
|
|
77
|
+
const started: number[] = [];
|
|
78
|
+
const items = Array.from({ length: 20 }, (_, i) => i);
|
|
79
|
+
await expect(
|
|
80
|
+
mapWithConcurrency(items, 2, async (item) => {
|
|
81
|
+
started.push(item);
|
|
82
|
+
if (item === 0) throw new Error("boom");
|
|
83
|
+
// Slow enough that without fail-fast, more workers would drain the list.
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
85
|
+
return item;
|
|
86
|
+
}),
|
|
87
|
+
).rejects.toThrow("boom");
|
|
88
|
+
// At most `limit` in-flight when the first throw sets `failed`; without
|
|
89
|
+
// the flag the other worker would keep claiming the rest of the 20.
|
|
90
|
+
expect(started.length).toBeLessThan(items.length);
|
|
91
|
+
expect(started.length).toBeLessThanOrEqual(3);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("empty input resolves to an empty array without running the worker", async () => {
|
|
95
|
+
const results = await mapWithConcurrency<number, number>([], 4, async (item) => item);
|
|
96
|
+
expect(results).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("limit larger than item count doesn't over-spawn workers", async () => {
|
|
100
|
+
const items = [1, 2];
|
|
101
|
+
const results = await mapWithConcurrency(items, 10, async (item) => item * 2);
|
|
102
|
+
expect(results).toEqual([2, 4]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Bounded-concurrency map — runs `fn` over `items` with at most `limit`
|
|
2
|
+
// calls in flight. `Promise.all(items.map(fn))` is unbounded (as many
|
|
3
|
+
// calls as rows); a plain sequential loop caps concurrency at 1, which
|
|
4
|
+
// under-uses a resource pool that allows more (e.g. PgKmsAdapter's
|
|
5
|
+
// default `max: 4` — sequential decrypt-per-row leaves 3 pool slots idle
|
|
6
|
+
// and turns each query into 2N serial round-trips instead of ~2N/4).
|
|
7
|
+
// Order of `results` matches `items`, independent of completion order.
|
|
8
|
+
// On rejection: the returned promise rejects with the first error, and
|
|
9
|
+
// remaining workers stop claiming new items (`failed` flag). In-flight
|
|
10
|
+
// calls already past the claim still run to completion (their promises
|
|
11
|
+
// stay in the `Promise.all` array, so a later rejection can't surface as
|
|
12
|
+
// an unhandled rejection — verified in `mapWithConcurrency.test.ts`).
|
|
13
|
+
export async function mapWithConcurrency<T, R>(
|
|
14
|
+
items: readonly T[],
|
|
15
|
+
limit: number,
|
|
16
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
17
|
+
): Promise<R[]> {
|
|
18
|
+
const results: R[] = new Array(items.length);
|
|
19
|
+
let nextIndex = 0;
|
|
20
|
+
let failed = false;
|
|
21
|
+
async function worker(): Promise<void> {
|
|
22
|
+
for (;;) {
|
|
23
|
+
// skip: another worker already failed — stop claiming new items.
|
|
24
|
+
if (failed) return;
|
|
25
|
+
const index = nextIndex++;
|
|
26
|
+
// skip: cursor exhausted, normal worker-loop exit
|
|
27
|
+
if (index >= items.length) return;
|
|
28
|
+
try {
|
|
29
|
+
results[index] = await fn(items[index] as T, index);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
failed = true;
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing";
|
|
3
|
+
|
|
4
|
+
const medianMs = async (fn: () => Promise<unknown>, runs = 5): Promise<number> => {
|
|
5
|
+
const samples: number[] = [];
|
|
6
|
+
for (let i = 0; i < runs; i++) {
|
|
7
|
+
const start = performance.now();
|
|
8
|
+
await fn();
|
|
9
|
+
samples.push(performance.now() - start);
|
|
10
|
+
}
|
|
11
|
+
samples.sort((a, b) => a - b);
|
|
12
|
+
return samples[Math.floor(samples.length / 2)] ?? 0;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// #774: the login no-user path must cost the same argon2 latency as a real
|
|
16
|
+
// verify, otherwise response timing leaks whether an email is registered.
|
|
17
|
+
describe("verifyDummyPassword (anti-enumeration timing)", () => {
|
|
18
|
+
test("resolves to void, never matches", async () => {
|
|
19
|
+
expect(await verifyDummyPassword("whatever")).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("burns real argon2 cost, comparable to a failed verify (not a no-op)", async () => {
|
|
23
|
+
const realHash = await hashPassword("correct-horse-battery");
|
|
24
|
+
// warm the lazily-cached dummy hash so we time the verify, not the one-off hash
|
|
25
|
+
await verifyDummyPassword("warmup");
|
|
26
|
+
|
|
27
|
+
const realMs = await medianMs(() => verifyPassword(realHash, "wrong-password"));
|
|
28
|
+
const dummyMs = await medianMs(() => verifyDummyPassword("wrong-password"));
|
|
29
|
+
|
|
30
|
+
// A skipped miss-path (the bug) is sub-millisecond; a real argon2 verify
|
|
31
|
+
// is ~20ms. A half-of-real floor is a wide, non-flaky bound that still
|
|
32
|
+
// fails hard if the dummy verify is ever removed.
|
|
33
|
+
expect(dummyMs).toBeGreaterThan(realMs * 0.5);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -42,3 +42,14 @@ export async function verifyPassword(hashString: string, password: string): Prom
|
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
// Anti-enumeration timing equaliser (#774). The login handler runs this on
|
|
47
|
+
// the no-user / no-hash path so a missing account costs the same argon2
|
|
48
|
+
// latency as a real verify — otherwise response timing leaks whether an
|
|
49
|
+
// email is registered. Derived from hashPassword once and cached, so it
|
|
50
|
+
// always tracks HASH_OPTIONS; the password never matches, result discarded.
|
|
51
|
+
let dummyHash: Promise<string> | undefined;
|
|
52
|
+
export async function verifyDummyPassword(password: string): Promise<void> {
|
|
53
|
+
dummyHash ??= hashPassword("anti-enumeration-dummy");
|
|
54
|
+
await verifyPassword(await dummyHash, password);
|
|
55
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Minimal shape of the httpRoute handler's `{ systemQuery }` dep — mirrors
|
|
2
|
+
// http-route.ts's signature. Not importing HttpRouteHandlerDeps itself: it
|
|
3
|
+
// isn't part of the engine's public surface (only the httpRoute-
|
|
4
|
+
// registration types are).
|
|
5
|
+
export type SystemQueryFn = (type: string, payload: unknown, tenantId: string) => Promise<unknown>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Single-use enforcement for HMAC-signed auth tokens (password-reset,
|
|
2
|
-
// email-verification).
|
|
2
|
+
// email-verification, MFA setup/challenge).
|
|
3
3
|
//
|
|
4
4
|
// Problem: the token itself carries only userId + expiry + signature.
|
|
5
5
|
// Without server-side burn, the same token can be replayed within its
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
// feature.ts contract tests for subscription-mollie.
|
|
2
2
|
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
|
+
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import type {
|
|
6
|
+
MollieClient,
|
|
7
|
+
Payment as MolliePayment,
|
|
8
|
+
Subscription as MollieSubscription,
|
|
9
|
+
} from "@mollie/api-client";
|
|
10
|
+
import { describeSubscriptionProviderContract } from "../../billing-foundation/__tests__/subscription-provider-contract";
|
|
4
11
|
import { MOLLIE_PROVIDER_NAME, SUBSCRIPTION_MOLLIE_FEATURE } from "../constants";
|
|
5
12
|
import { createSubscriptionMollieFeature } from "../feature";
|
|
13
|
+
import { createMollieCheckoutSession } from "../plugin-methods";
|
|
14
|
+
import { type MollieClientShape, verifyAndParseMollieWebhook } from "../verify-webhook";
|
|
6
15
|
|
|
7
16
|
const VALID_OPTIONS = {
|
|
8
17
|
apiKey: "test_dummy_apikey",
|
|
@@ -98,9 +107,97 @@ describe("subscription-mollie — plugin-registration", () => {
|
|
|
98
107
|
createPortalSession?: unknown;
|
|
99
108
|
cancelSubscription?: unknown;
|
|
100
109
|
};
|
|
110
|
+
// Checks the mounted feature (the contract below builds its plugin
|
|
111
|
+
// by hand-wiring and would not catch a wiring gap here).
|
|
101
112
|
expect(typeof plugin?.verifyAndParseWebhook).toBe("function");
|
|
102
113
|
expect(typeof plugin?.createCheckoutSession).toBe("function");
|
|
103
114
|
expect(plugin?.createPortalSession).toBeUndefined();
|
|
104
115
|
expect(plugin?.cancelSubscription).toBeUndefined();
|
|
105
116
|
});
|
|
106
117
|
});
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// SubscriptionProviderPlugin contract
|
|
121
|
+
// =============================================================================
|
|
122
|
+
//
|
|
123
|
+
// Built directly from the plugin-method constructors (not from
|
|
124
|
+
// createSubscriptionMollieFeature's mounted plugin) — same reasoning as
|
|
125
|
+
// subscription-stripe's contract fixture: keeps SDK-call mocking local to
|
|
126
|
+
// the fixture instead of routing through the full feature-mount.
|
|
127
|
+
|
|
128
|
+
const CONTRACT_TENANT_ID = "tenant-contract";
|
|
129
|
+
const CONTRACT_PRICE_ID = "plan_pro";
|
|
130
|
+
|
|
131
|
+
function buildMollieContractFixture() {
|
|
132
|
+
const subscription = {
|
|
133
|
+
id: "sub_contract_001",
|
|
134
|
+
customerId: "cst_contract_001",
|
|
135
|
+
status: "active",
|
|
136
|
+
nextPaymentDate: "2026-06-15",
|
|
137
|
+
startDate: "2026-05-15",
|
|
138
|
+
metadata: { tenantId: CONTRACT_TENANT_ID, priceId: CONTRACT_PRICE_ID },
|
|
139
|
+
// Minimal Mollie-SDK-shape mock — full Subscription has more fields
|
|
140
|
+
// than the plugin reads; double-cast at the test/SDK boundary instead
|
|
141
|
+
// of `as any` per coding-standards.
|
|
142
|
+
} as unknown as MollieSubscription;
|
|
143
|
+
const payment = {
|
|
144
|
+
id: "tr_contract_001",
|
|
145
|
+
customerId: "cst_contract_001",
|
|
146
|
+
subscriptionId: "sub_contract_001",
|
|
147
|
+
sequenceType: "first",
|
|
148
|
+
status: "paid",
|
|
149
|
+
// Minimal Mollie-SDK-shape mock, see subscription above.
|
|
150
|
+
} as unknown as MolliePayment;
|
|
151
|
+
|
|
152
|
+
const webhookClient: MollieClientShape = {
|
|
153
|
+
payments: { get: async () => payment },
|
|
154
|
+
customerSubscriptions: {
|
|
155
|
+
get: async () => subscription,
|
|
156
|
+
list: async () => [],
|
|
157
|
+
create: async () => subscription,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const checkoutClient = {
|
|
162
|
+
customers: { create: async () => ({ id: "cus_contract" }) },
|
|
163
|
+
payments: {
|
|
164
|
+
create: async () => ({
|
|
165
|
+
getCheckoutUrl: () => "https://www.mollie.com/checkout/contract-test",
|
|
166
|
+
}),
|
|
167
|
+
},
|
|
168
|
+
} as unknown as MollieClient;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
plugin: {
|
|
172
|
+
verifyAndParseWebhook: verifyAndParseMollieWebhook(webhookClient, {
|
|
173
|
+
priceToTier: VALID_OPTIONS.priceToTier,
|
|
174
|
+
priceToConfig: VALID_OPTIONS.priceToConfig,
|
|
175
|
+
}),
|
|
176
|
+
createCheckoutSession: createMollieCheckoutSession(
|
|
177
|
+
checkoutClient,
|
|
178
|
+
VALID_OPTIONS.priceToConfig,
|
|
179
|
+
VALID_OPTIONS.webhookUrl,
|
|
180
|
+
),
|
|
181
|
+
// createPortalSession + cancelSubscription deliberately omitted — Mollie limitation.
|
|
182
|
+
},
|
|
183
|
+
ctx: {} as HandlerContext,
|
|
184
|
+
checkout: {
|
|
185
|
+
priceId: CONTRACT_PRICE_ID,
|
|
186
|
+
tenantId: CONTRACT_TENANT_ID,
|
|
187
|
+
successUrl: "https://example.com/success",
|
|
188
|
+
cancelUrl: "https://example.com/cancel",
|
|
189
|
+
},
|
|
190
|
+
webhook: {
|
|
191
|
+
rawBody: JSON.stringify({ id: "tr_contract_001" }),
|
|
192
|
+
headers: { "content-type": "application/json" },
|
|
193
|
+
expectedTenantId: CONTRACT_TENANT_ID,
|
|
194
|
+
expectedTier: "pro",
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
describeSubscriptionProviderContract("subscription-mollie", buildMollieContractFixture, {
|
|
200
|
+
// Mollie has no customer-portal or programmatic cancel API.
|
|
201
|
+
hasPortal: false,
|
|
202
|
+
hasCancel: false,
|
|
203
|
+
});
|
|
@@ -39,6 +39,10 @@ import type {
|
|
|
39
39
|
Subscription as MollieSubscription,
|
|
40
40
|
} from "@mollie/api-client";
|
|
41
41
|
import { Hono } from "hono";
|
|
42
|
+
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
43
|
+
import { createConfigFeature } from "../../config";
|
|
44
|
+
import { createTenantFeature } from "../../tenant/feature";
|
|
45
|
+
import { createTenantLifecycleFeature } from "../../tenant-lifecycle";
|
|
42
46
|
import { createSubscriptionMollieFeature } from "../feature";
|
|
43
47
|
import type { MollieClientShape } from "../verify-webhook";
|
|
44
48
|
|
|
@@ -133,7 +137,14 @@ beforeAll(async () => {
|
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
stack = await setupTestStack({
|
|
136
|
-
features: [
|
|
140
|
+
features: [
|
|
141
|
+
createConfigFeature(),
|
|
142
|
+
createTenantFeature(),
|
|
143
|
+
createComplianceProfilesFeature(),
|
|
144
|
+
createTenantLifecycleFeature(),
|
|
145
|
+
billingFoundationFeature,
|
|
146
|
+
mollieFeature,
|
|
147
|
+
],
|
|
137
148
|
});
|
|
138
149
|
db = stack.db;
|
|
139
150
|
// subscriptionsProjectionTable wird von setupTestStack automatisch
|
|
@@ -347,6 +347,27 @@ describe("verifyAndParseMollieWebhook — error + ignore paths", () => {
|
|
|
347
347
|
});
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
+
describe("verifyAndParseMollieWebhook — kumiko-framework#1525: no ambient Temporal global", () => {
|
|
351
|
+
const verify = (client: MollieClientShape) =>
|
|
352
|
+
verifyAndParseMollieWebhook(client, {
|
|
353
|
+
priceToTier: { plan_pro: "pro" },
|
|
354
|
+
priceToConfig: TEST_PRICE_CONFIG,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("computes currentPeriodEnd without relying on globalThis.Temporal", async () => {
|
|
358
|
+
const client = buildClient();
|
|
359
|
+
const savedGlobal = (globalThis as { Temporal?: unknown }).Temporal;
|
|
360
|
+
delete (globalThis as { Temporal?: unknown }).Temporal;
|
|
361
|
+
try {
|
|
362
|
+
const event = await verify(client)("id=tr_test_001", {});
|
|
363
|
+
expect(event?.currentPeriodEnd).toBe("2026-06-15T00:00:00Z");
|
|
364
|
+
} finally {
|
|
365
|
+
if (savedGlobal === undefined) delete (globalThis as { Temporal?: unknown }).Temporal;
|
|
366
|
+
else (globalThis as { Temporal?: unknown }).Temporal = savedGlobal;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
350
371
|
// =============================================================================
|
|
351
372
|
// Mapping-helpers
|
|
352
373
|
// =============================================================================
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
Payment as MolliePayment,
|
|
26
26
|
Subscription as MollieSubscription,
|
|
27
27
|
} from "@mollie/api-client";
|
|
28
|
+
import { Temporal } from "temporal-polyfill";
|
|
28
29
|
import { MOLLIE_PROVIDER_NAME } from "./constants";
|
|
29
30
|
import type { MolliePriceConfig } from "./plugin-methods";
|
|
30
31
|
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// feature.ts contract tests for subscription-stripe.
|
|
2
2
|
|
|
3
|
-
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import { describe, expect, spyOn, test } from "bun:test";
|
|
4
|
+
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import Stripe from "stripe";
|
|
6
|
+
import { describeSubscriptionProviderContract } from "../../billing-foundation/__tests__/subscription-provider-contract";
|
|
4
7
|
import { STRIPE_PROVIDER_NAME, StripeEventTypes, SUBSCRIPTION_STRIPE_FEATURE } from "../constants";
|
|
5
8
|
import { createSubscriptionStripeFeature } from "../feature";
|
|
9
|
+
import {
|
|
10
|
+
createStripeCancelSubscription,
|
|
11
|
+
createStripeCheckoutSession,
|
|
12
|
+
createStripePortalSession,
|
|
13
|
+
} from "../plugin-methods";
|
|
14
|
+
import type { StripeCtxRuntime } from "../runtime";
|
|
15
|
+
import { verifyAndParseStripeWebhook } from "../verify-webhook";
|
|
6
16
|
|
|
7
17
|
const OPTIONS = {
|
|
8
18
|
priceToTier: { price_test: "pro" },
|
|
@@ -69,6 +79,8 @@ describe("subscription-stripe — plugin-registration", () => {
|
|
|
69
79
|
// "method not supported"-error brechen — type-check würde es nicht
|
|
70
80
|
// fangen weil die useExtension-options als `unknown` durchgereicht
|
|
71
81
|
// werden.
|
|
82
|
+
// Checks the mounted feature — the contract below builds its plugin
|
|
83
|
+
// by hand-wiring, so it would not catch a wiring gap here.
|
|
72
84
|
const feature = createSubscriptionStripeFeature(OPTIONS);
|
|
73
85
|
const usage = feature.extensionUsages.find((u) => u.entityName === STRIPE_PROVIDER_NAME);
|
|
74
86
|
expect(usage).toBeDefined();
|
|
@@ -100,3 +112,117 @@ describe("constants — Stripe-event-types die wir mappen", () => {
|
|
|
100
112
|
]);
|
|
101
113
|
});
|
|
102
114
|
});
|
|
115
|
+
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// SubscriptionProviderPlugin contract
|
|
118
|
+
// =============================================================================
|
|
119
|
+
//
|
|
120
|
+
// Built directly from the plugin-method constructors (not from
|
|
121
|
+
// createSubscriptionStripeFeature's mounted plugin) — the mounted plugin
|
|
122
|
+
// resolves its Stripe client per-call from ctx.config/ctx.secrets, which
|
|
123
|
+
// can't be spied on from outside. plugin-methods.test.ts already exercises
|
|
124
|
+
// this same runtime-injection pattern for the individual methods.
|
|
125
|
+
|
|
126
|
+
const CONTRACT_API_KEY = "sk_test_contract_dummy";
|
|
127
|
+
const CONTRACT_WEBHOOK_SECRET = "whsec_contract_test_secret";
|
|
128
|
+
|
|
129
|
+
function contractCtxRuntime(stripe: Stripe): StripeCtxRuntime {
|
|
130
|
+
return { clientForCtx: async () => stripe, assertBillingLive: async () => {} };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function buildStripeContractFixture() {
|
|
134
|
+
const stripe = new Stripe(CONTRACT_API_KEY);
|
|
135
|
+
spyOn(stripe.checkout.sessions, "create").mockResolvedValue({
|
|
136
|
+
url: "https://checkout.stripe.com/c/pay/contract-test",
|
|
137
|
+
// Only `.url` is read by createStripeCheckoutSession. mockResolvedValue
|
|
138
|
+
// wants Stripe.Response<Session> (adds lastResponse metadata) — the SDK
|
|
139
|
+
// wrapper type makes a structural mock impractical, so double-cast at
|
|
140
|
+
// the test/SDK boundary instead of `as any`.
|
|
141
|
+
} as unknown as Stripe.Response<Stripe.Checkout.Session>);
|
|
142
|
+
spyOn(stripe.billingPortal.sessions, "create").mockResolvedValue({
|
|
143
|
+
url: "https://billing.stripe.com/p/session/contract-test",
|
|
144
|
+
// Only `.url` is read by createStripePortalSession, see above.
|
|
145
|
+
} as unknown as Stripe.Response<Stripe.BillingPortal.Session>);
|
|
146
|
+
spyOn(stripe.subscriptions, "cancel").mockResolvedValue({
|
|
147
|
+
id: "sub_contract",
|
|
148
|
+
status: "canceled",
|
|
149
|
+
// Return value isn't read by createStripeCancelSubscription (fire-and-
|
|
150
|
+
// forget) — double-cast at the test/SDK boundary instead of `as any`.
|
|
151
|
+
} as unknown as Stripe.Response<Stripe.Subscription>);
|
|
152
|
+
const runtime = contractCtxRuntime(stripe);
|
|
153
|
+
|
|
154
|
+
const stripeForWebhookFixture = new Stripe(CONTRACT_API_KEY);
|
|
155
|
+
const webhookEvent = {
|
|
156
|
+
id: "evt_contract_001",
|
|
157
|
+
object: "event",
|
|
158
|
+
api_version: "2026-04-22.dahlia",
|
|
159
|
+
created: 1_770_000_000,
|
|
160
|
+
type: StripeEventTypes.customerSubscriptionCreated,
|
|
161
|
+
livemode: false,
|
|
162
|
+
pending_webhooks: 1,
|
|
163
|
+
request: { id: null, idempotency_key: null },
|
|
164
|
+
data: {
|
|
165
|
+
object: {
|
|
166
|
+
id: "sub_contract_001",
|
|
167
|
+
object: "subscription",
|
|
168
|
+
customer: "cus_contract_001",
|
|
169
|
+
status: "active",
|
|
170
|
+
metadata: { tenantId: "tenant-contract" },
|
|
171
|
+
items: {
|
|
172
|
+
object: "list",
|
|
173
|
+
data: [
|
|
174
|
+
{
|
|
175
|
+
id: "si_contract",
|
|
176
|
+
object: "subscription_item",
|
|
177
|
+
current_period_end: 1_780_000_000,
|
|
178
|
+
price: { id: "price_test", object: "price" },
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
has_more: false,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
const rawBody = JSON.stringify(webhookEvent);
|
|
187
|
+
const signature = await stripeForWebhookFixture.webhooks.generateTestHeaderStringAsync({
|
|
188
|
+
payload: rawBody,
|
|
189
|
+
secret: CONTRACT_WEBHOOK_SECRET,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
plugin: {
|
|
194
|
+
verifyAndParseWebhook: verifyAndParseStripeWebhook(
|
|
195
|
+
{
|
|
196
|
+
resolve: async () => ({
|
|
197
|
+
stripe: stripeForWebhookFixture,
|
|
198
|
+
webhookSecret: CONTRACT_WEBHOOK_SECRET,
|
|
199
|
+
}),
|
|
200
|
+
},
|
|
201
|
+
{ priceToTier: OPTIONS.priceToTier },
|
|
202
|
+
),
|
|
203
|
+
createCheckoutSession: createStripeCheckoutSession(runtime),
|
|
204
|
+
createPortalSession: createStripePortalSession(runtime),
|
|
205
|
+
cancelSubscription: createStripeCancelSubscription(runtime),
|
|
206
|
+
},
|
|
207
|
+
ctx: {} as HandlerContext,
|
|
208
|
+
checkout: {
|
|
209
|
+
priceId: "price_test",
|
|
210
|
+
tenantId: "tenant-contract",
|
|
211
|
+
successUrl: "https://example.com/success",
|
|
212
|
+
cancelUrl: "https://example.com/cancel",
|
|
213
|
+
},
|
|
214
|
+
portal: { providerCustomerId: "cus_contract", returnUrl: "https://example.com/return" },
|
|
215
|
+
cancelSubscriptionId: "sub_contract",
|
|
216
|
+
webhook: {
|
|
217
|
+
rawBody,
|
|
218
|
+
headers: { "stripe-signature": signature },
|
|
219
|
+
expectedTenantId: "tenant-contract",
|
|
220
|
+
expectedTier: "pro",
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
describeSubscriptionProviderContract("subscription-stripe", buildStripeContractFixture, {
|
|
226
|
+
hasPortal: true,
|
|
227
|
+
hasCancel: true,
|
|
228
|
+
});
|
|
@@ -44,34 +44,24 @@ const BILLING_LIVE_HANDLE: ConfigKeyHandle<"boolean"> = {
|
|
|
44
44
|
|
|
45
45
|
/** Stub-SecretsContext: liest aus einer in-memory-map, matcht per
|
|
46
46
|
* qualified-name. backing:"secrets" persistiert config-Werte JSON-
|
|
47
|
-
* serialisiert — der Stub spiegelt das, damit der Runtime-
|
|
48
|
-
* denselben Pfad nimmt wie gegen den echten Store.
|
|
49
|
-
|
|
47
|
+
* serialisiert — der Stub spiegelt das per Default, damit der Runtime-
|
|
48
|
+
* parseStoredSecret denselben Pfad nimmt wie gegen den echten Store.
|
|
49
|
+
* `raw: true` speichert UN-serialisiert (482/1) — um parseStoredSecret's
|
|
50
|
+
* Fehlerpfad zu treffen: ein Credential, das der Store un-JSON-kodiert
|
|
51
|
+
* zurückgibt (Korruption oder außerhalb des backing:"secrets"-Roundtrips
|
|
52
|
+
* geschrieben) muss laut failen, nicht still Müll liefern. Ignoriert
|
|
53
|
+
* auditCtx. */
|
|
54
|
+
function stubSecrets(
|
|
55
|
+
values: Record<string, string>,
|
|
56
|
+
opts?: { readonly raw?: boolean },
|
|
57
|
+
): SecretsContext {
|
|
50
58
|
const nameOf = (k: string | { readonly name: string }): string =>
|
|
51
59
|
typeof k === "string" ? k : k.name;
|
|
52
60
|
return {
|
|
53
61
|
get: async (_tenantId, key) => {
|
|
54
62
|
const value = values[nameOf(key)];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
has: async (_tenantId, key) => values[nameOf(key)] !== undefined,
|
|
58
|
-
set: async () => undefined,
|
|
59
|
-
delete: async () => false,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Wie stubSecrets, aber speichert den Wert ROH (kein JSON.stringify) — um
|
|
64
|
-
* parseStoredSecret's Fehlerpfad zu treffen: ein Credential, das der Store
|
|
65
|
-
* un-JSON-kodiert zurückgibt (Korruption oder ein außerhalb des
|
|
66
|
-
* backing:"secrets"-Roundtrips geschriebener Wert) muss laut failen, nicht
|
|
67
|
-
* still Müll liefern. */
|
|
68
|
-
function rawSecretsStub(values: Record<string, string>): SecretsContext {
|
|
69
|
-
const nameOf = (k: string | { readonly name: string }): string =>
|
|
70
|
-
typeof k === "string" ? k : k.name;
|
|
71
|
-
return {
|
|
72
|
-
get: async (_tenantId, key) => {
|
|
73
|
-
const value = values[nameOf(key)];
|
|
74
|
-
return value === undefined ? undefined : createSecret(value); // RAW, not JSON
|
|
63
|
+
if (value === undefined) return undefined;
|
|
64
|
+
return createSecret(opts?.raw ? value : JSON.stringify(value));
|
|
75
65
|
},
|
|
76
66
|
has: async (_tenantId, key) => values[nameOf(key)] !== undefined,
|
|
77
67
|
set: async () => undefined,
|
|
@@ -164,7 +154,7 @@ describe("StripeCtxRuntime.clientForCtx", () => {
|
|
|
164
154
|
// throw, not silently fall through to undefined/fallback.
|
|
165
155
|
const rt = makeRuntimes({ apiKey: "sk_test_fallback" });
|
|
166
156
|
const ctx = stubCtx({
|
|
167
|
-
secrets:
|
|
157
|
+
secrets: stubSecrets({ [API_KEY_HANDLE.name]: "sk_test_raw_unquoted" }, { raw: true }),
|
|
168
158
|
});
|
|
169
159
|
await expect(rt.ctx.clientForCtx(ctx)).rejects.toThrow(
|
|
170
160
|
/Invalid JSON in subscription-stripe credential/,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type SubscriptionProviderPlugin,
|
|
21
21
|
subscriptionAggregateId,
|
|
22
22
|
} from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
23
|
-
import {
|
|
23
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
24
24
|
import { SYSTEM_TENANT_ID, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
25
25
|
import { createEventsTable, loadAggregate } from "@cosmicdrift/kumiko-framework/event-store";
|
|
26
26
|
import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
@@ -31,8 +31,10 @@ import {
|
|
|
31
31
|
testTenantId,
|
|
32
32
|
unsafePushTables,
|
|
33
33
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
34
|
+
import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
|
|
34
35
|
import { Hono } from "hono";
|
|
35
36
|
import Stripe from "stripe";
|
|
37
|
+
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
36
38
|
import { configValuesTable, createConfigFeature } from "../../config";
|
|
37
39
|
import { createConfigAccessorFactory } from "../../config/feature";
|
|
38
40
|
import { createConfigResolver } from "../../config/resolver";
|
|
@@ -42,6 +44,8 @@ import {
|
|
|
42
44
|
type SecretsContext,
|
|
43
45
|
tenantSecretsTable,
|
|
44
46
|
} from "../../secrets";
|
|
47
|
+
import { createTenantFeature } from "../../tenant/feature";
|
|
48
|
+
import { createTenantLifecycleFeature } from "../../tenant-lifecycle";
|
|
45
49
|
import { createSubscriptionStripeFeature } from "../feature";
|
|
46
50
|
|
|
47
51
|
// Qualified-names der runtime-keys (drift-pin: müssen 1:1 dem entsprechen,
|
|
@@ -79,8 +83,8 @@ beforeAll(async () => {
|
|
|
79
83
|
priceToTier: PRICE_TO_TIER,
|
|
80
84
|
});
|
|
81
85
|
|
|
82
|
-
const encryption =
|
|
83
|
-
const resolver = createConfigResolver({ encryption });
|
|
86
|
+
const encryption = createTestEnvelopeCipher(randomBytes(32).toString("base64"));
|
|
87
|
+
const resolver = createConfigResolver({ cipher: encryption });
|
|
84
88
|
const masterKeyProvider = createEnvMasterKeyProvider({
|
|
85
89
|
env: {
|
|
86
90
|
KUMIKO_SECRETS_MASTER_KEY_V1: randomBytes(32).toString("base64"),
|
|
@@ -92,6 +96,9 @@ beforeAll(async () => {
|
|
|
92
96
|
features: [
|
|
93
97
|
createConfigFeature(),
|
|
94
98
|
createSecretsFeature(),
|
|
99
|
+
createTenantFeature(),
|
|
100
|
+
createComplianceProfilesFeature(),
|
|
101
|
+
createTenantLifecycleFeature(),
|
|
95
102
|
billingFoundationFeature,
|
|
96
103
|
stripeFeature,
|
|
97
104
|
],
|
|
@@ -470,8 +477,8 @@ describe("scenario 6: billing-live gate end-to-end (#104)", () => {
|
|
|
470
477
|
|
|
471
478
|
beforeAll(async () => {
|
|
472
479
|
const stripeFeature = createSubscriptionStripeFeature({ priceToTier: PRICE_TO_TIER });
|
|
473
|
-
const encryption =
|
|
474
|
-
const resolver = createConfigResolver({ encryption });
|
|
480
|
+
const encryption = createTestEnvelopeCipher(randomBytes(32).toString("base64"));
|
|
481
|
+
const resolver = createConfigResolver({ cipher: encryption });
|
|
475
482
|
const masterKeyProvider = createEnvMasterKeyProvider({
|
|
476
483
|
env: {
|
|
477
484
|
KUMIKO_SECRETS_MASTER_KEY_V1: randomBytes(32).toString("base64"),
|
|
@@ -482,6 +489,9 @@ describe("scenario 6: billing-live gate end-to-end (#104)", () => {
|
|
|
482
489
|
features: [
|
|
483
490
|
createConfigFeature(),
|
|
484
491
|
createSecretsFeature(),
|
|
492
|
+
createTenantFeature(),
|
|
493
|
+
createComplianceProfilesFeature(),
|
|
494
|
+
createTenantLifecycleFeature(),
|
|
485
495
|
billingFoundationFeature,
|
|
486
496
|
stripeFeature,
|
|
487
497
|
],
|