@wopr-network/platform-core 0.1.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/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { EmailClient } from "./client.js";
|
|
3
|
+
import type {
|
|
4
|
+
INotificationPreferencesRepository,
|
|
5
|
+
INotificationQueueRepository,
|
|
6
|
+
QueuedNotification,
|
|
7
|
+
} from "./notification-repository-types.js";
|
|
8
|
+
import { NotificationWorker } from "./notification-worker.js";
|
|
9
|
+
|
|
10
|
+
vi.mock("../config/logger.js", () => ({
|
|
11
|
+
logger: {
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
error: vi.fn(),
|
|
14
|
+
warn: vi.fn(),
|
|
15
|
+
debug: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function makeNotif(overrides: Partial<QueuedNotification> = {}): QueuedNotification {
|
|
20
|
+
return {
|
|
21
|
+
id: "notif-1",
|
|
22
|
+
tenantId: "tenant-1",
|
|
23
|
+
template: "low-balance",
|
|
24
|
+
data: JSON.stringify({ email: "user@example.com", balanceDollars: "$1.00" }),
|
|
25
|
+
status: "pending",
|
|
26
|
+
attempts: 0,
|
|
27
|
+
retryAfter: null,
|
|
28
|
+
sentAt: null,
|
|
29
|
+
createdAt: Date.now(),
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeQueue(pending: QueuedNotification[] = []): INotificationQueueRepository {
|
|
35
|
+
return {
|
|
36
|
+
enqueue: vi.fn().mockReturnValue("notif-id"),
|
|
37
|
+
fetchPending: vi.fn().mockReturnValue(pending),
|
|
38
|
+
markSent: vi.fn(),
|
|
39
|
+
markFailed: vi.fn(),
|
|
40
|
+
listForTenant: vi.fn().mockReturnValue({ entries: [], total: 0 }),
|
|
41
|
+
} as unknown as INotificationQueueRepository;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makePrefs(prefs: Record<string, boolean> = {}): INotificationPreferencesRepository {
|
|
45
|
+
const defaultPrefs = {
|
|
46
|
+
billing_low_balance: true,
|
|
47
|
+
billing_receipts: true,
|
|
48
|
+
billing_auto_topup: true,
|
|
49
|
+
agent_channel_disconnect: true,
|
|
50
|
+
agent_status_changes: false,
|
|
51
|
+
account_role_changes: true,
|
|
52
|
+
account_team_invites: true,
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
get: vi.fn().mockReturnValue({ ...defaultPrefs, ...prefs }),
|
|
56
|
+
update: vi.fn(),
|
|
57
|
+
} as unknown as INotificationPreferencesRepository;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeEmailClient(): EmailClient {
|
|
61
|
+
return {
|
|
62
|
+
send: vi.fn().mockResolvedValue({ id: "email-123", success: true }),
|
|
63
|
+
onEmailSent: vi.fn(),
|
|
64
|
+
} as unknown as EmailClient;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("NotificationWorker", () => {
|
|
68
|
+
let emailClient: EmailClient;
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
emailClient = makeEmailClient();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("processBatch", () => {
|
|
75
|
+
it("sends emails for pending notifications and marks them sent", async () => {
|
|
76
|
+
const notif = makeNotif();
|
|
77
|
+
const queue = makeQueue([notif]);
|
|
78
|
+
const prefs = makePrefs();
|
|
79
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
80
|
+
|
|
81
|
+
const count = await worker.processBatch();
|
|
82
|
+
|
|
83
|
+
expect(emailClient.send).toHaveBeenCalledOnce();
|
|
84
|
+
expect(queue.markSent).toHaveBeenCalledWith("notif-1");
|
|
85
|
+
expect(count).toBe(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns 0 when no pending notifications", async () => {
|
|
89
|
+
const queue = makeQueue([]);
|
|
90
|
+
const prefs = makePrefs();
|
|
91
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
92
|
+
|
|
93
|
+
const count = await worker.processBatch();
|
|
94
|
+
expect(count).toBe(0);
|
|
95
|
+
expect(emailClient.send).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("marks notification as failed (not sent) when email is missing", async () => {
|
|
99
|
+
const notif = makeNotif({ data: JSON.stringify({}) }); // no email
|
|
100
|
+
const queue = makeQueue([notif]);
|
|
101
|
+
const prefs = makePrefs();
|
|
102
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
103
|
+
|
|
104
|
+
await worker.processBatch();
|
|
105
|
+
|
|
106
|
+
expect(emailClient.send).not.toHaveBeenCalled();
|
|
107
|
+
expect(queue.markFailed).toHaveBeenCalledWith("notif-1", 1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("marks as sent (skipped) when user preference disables that template", async () => {
|
|
111
|
+
const notif = makeNotif({ template: "agent-created" }); // pref: agent_status_changes
|
|
112
|
+
const queue = makeQueue([notif]);
|
|
113
|
+
const prefs = makePrefs({ agent_status_changes: false }); // disabled
|
|
114
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
115
|
+
|
|
116
|
+
await worker.processBatch();
|
|
117
|
+
|
|
118
|
+
expect(emailClient.send).not.toHaveBeenCalled();
|
|
119
|
+
expect(queue.markSent).toHaveBeenCalledWith("notif-1"); // cleared from queue
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("sends critical templates even when preferences would disable them", async () => {
|
|
123
|
+
// grace-period-start is critical — should bypass preference check
|
|
124
|
+
const notif = makeNotif({
|
|
125
|
+
template: "grace-period-start",
|
|
126
|
+
data: JSON.stringify({
|
|
127
|
+
email: "user@example.com",
|
|
128
|
+
balanceDollars: "$0.00",
|
|
129
|
+
graceDays: 7,
|
|
130
|
+
creditsUrl: "https://app.wopr.bot/billing/credits",
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
const queue = makeQueue([notif]);
|
|
134
|
+
// Even if all prefs disabled, critical templates must send
|
|
135
|
+
const prefs = makePrefs({
|
|
136
|
+
billing_low_balance: false,
|
|
137
|
+
billing_receipts: false,
|
|
138
|
+
billing_auto_topup: false,
|
|
139
|
+
});
|
|
140
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
141
|
+
|
|
142
|
+
await worker.processBatch();
|
|
143
|
+
|
|
144
|
+
expect(emailClient.send).toHaveBeenCalledOnce();
|
|
145
|
+
expect(queue.markSent).toHaveBeenCalledWith(notif.id);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("marks as failed and increments attempts when send throws", async () => {
|
|
149
|
+
const notif = makeNotif({ attempts: 2 });
|
|
150
|
+
const queue = makeQueue([notif]);
|
|
151
|
+
const prefs = makePrefs();
|
|
152
|
+
vi.spyOn(emailClient, "send").mockRejectedValueOnce(new Error("Network error"));
|
|
153
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
154
|
+
|
|
155
|
+
await worker.processBatch();
|
|
156
|
+
|
|
157
|
+
expect(queue.markFailed).toHaveBeenCalledWith("notif-1", 3); // attempts + 1
|
|
158
|
+
expect(queue.markSent).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("processes multiple notifications in one batch", async () => {
|
|
162
|
+
const notif1 = makeNotif({ id: "n1" });
|
|
163
|
+
const notif2 = makeNotif({
|
|
164
|
+
id: "n2",
|
|
165
|
+
template: "welcome",
|
|
166
|
+
data: JSON.stringify({ email: "b@b.com" }),
|
|
167
|
+
});
|
|
168
|
+
const queue = makeQueue([notif1, notif2]);
|
|
169
|
+
const prefs = makePrefs();
|
|
170
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs });
|
|
171
|
+
|
|
172
|
+
const count = await worker.processBatch();
|
|
173
|
+
|
|
174
|
+
expect(count).toBe(2);
|
|
175
|
+
expect(emailClient.send).toHaveBeenCalledTimes(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("respects custom batchSize", async () => {
|
|
179
|
+
const notifs = Array.from({ length: 5 }, (_, i) => makeNotif({ id: `n${i}` }));
|
|
180
|
+
const queue = makeQueue(notifs.slice(0, 3)); // fetchPending called with batchSize
|
|
181
|
+
const prefs = makePrefs();
|
|
182
|
+
const worker = new NotificationWorker({ queue, emailClient, preferences: prefs, batchSize: 3 });
|
|
183
|
+
|
|
184
|
+
await worker.processBatch();
|
|
185
|
+
|
|
186
|
+
expect(queue.fetchPending).toHaveBeenCalledWith(3);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationWorker — processes pending notification queue entries.
|
|
3
|
+
*
|
|
4
|
+
* Called on a timer (e.g. every 30s) from the server startup code.
|
|
5
|
+
* Do NOT put the interval inside this class.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logger } from "../config/logger.js";
|
|
9
|
+
import type { EmailClient } from "./client.js";
|
|
10
|
+
import type {
|
|
11
|
+
INotificationPreferencesRepository,
|
|
12
|
+
INotificationQueueRepository,
|
|
13
|
+
} from "./notification-repository-types.js";
|
|
14
|
+
import { renderNotificationTemplate, type TemplateName } from "./notification-templates.js";
|
|
15
|
+
|
|
16
|
+
export interface NotificationWorkerConfig {
|
|
17
|
+
queue: INotificationQueueRepository;
|
|
18
|
+
emailClient: EmailClient;
|
|
19
|
+
preferences: INotificationPreferencesRepository;
|
|
20
|
+
batchSize?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Templates that bypass user preference checks — always sent. */
|
|
24
|
+
const CRITICAL_TEMPLATES: Set<string> = new Set([
|
|
25
|
+
"grace-period-start",
|
|
26
|
+
"grace-period-warning",
|
|
27
|
+
"auto-suspended",
|
|
28
|
+
"admin-suspended",
|
|
29
|
+
"admin-reactivated",
|
|
30
|
+
"password-reset",
|
|
31
|
+
"welcome",
|
|
32
|
+
"account-deletion-requested",
|
|
33
|
+
"account-deletion-cancelled",
|
|
34
|
+
"account-deletion-completed",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** Map from template name to preference key. */
|
|
38
|
+
const PREF_MAP: Record<string, string> = {
|
|
39
|
+
"low-balance": "billing_low_balance",
|
|
40
|
+
"credits-depleted": "billing_low_balance",
|
|
41
|
+
"auto-topup-success": "billing_auto_topup",
|
|
42
|
+
"auto-topup-failed": "billing_auto_topup",
|
|
43
|
+
"credit-purchase-receipt": "billing_receipts",
|
|
44
|
+
"crypto-payment-confirmed": "billing_receipts",
|
|
45
|
+
"channel-disconnected": "agent_channel_disconnect",
|
|
46
|
+
"agent-created": "agent_status_changes",
|
|
47
|
+
"channel-connected": "agent_status_changes",
|
|
48
|
+
"agent-suspended": "agent_status_changes",
|
|
49
|
+
"credits-granted": "billing_receipts",
|
|
50
|
+
"dividend-weekly-digest": "billing_receipts",
|
|
51
|
+
"role-changed": "account_role_changes",
|
|
52
|
+
"team-invite": "account_team_invites",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class NotificationWorker {
|
|
56
|
+
private readonly queue: INotificationQueueRepository;
|
|
57
|
+
private readonly emailClient: EmailClient;
|
|
58
|
+
private readonly preferences: INotificationPreferencesRepository;
|
|
59
|
+
private readonly batchSize: number;
|
|
60
|
+
|
|
61
|
+
constructor(config: NotificationWorkerConfig) {
|
|
62
|
+
this.queue = config.queue;
|
|
63
|
+
this.emailClient = config.emailClient;
|
|
64
|
+
this.preferences = config.preferences;
|
|
65
|
+
this.batchSize = config.batchSize ?? 10;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Process one batch of pending notifications. Returns count of processed items. */
|
|
69
|
+
async processBatch(): Promise<number> {
|
|
70
|
+
const pending = await this.queue.fetchPending(this.batchSize);
|
|
71
|
+
let processed = 0;
|
|
72
|
+
|
|
73
|
+
for (const notif of pending) {
|
|
74
|
+
try {
|
|
75
|
+
const data = JSON.parse(notif.data) as Record<string, unknown>;
|
|
76
|
+
const email = data.email as string | undefined;
|
|
77
|
+
|
|
78
|
+
if (!email) {
|
|
79
|
+
logger.error("Notification missing email field", {
|
|
80
|
+
notificationId: notif.id,
|
|
81
|
+
template: notif.template,
|
|
82
|
+
});
|
|
83
|
+
this.queue.markFailed(notif.id, notif.attempts + 1);
|
|
84
|
+
processed++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check preferences (skip for critical notifications)
|
|
89
|
+
if (!CRITICAL_TEMPLATES.has(notif.template)) {
|
|
90
|
+
const prefs = this.preferences.get(notif.tenantId);
|
|
91
|
+
if (!this.isEnabledByPreferences(notif.template, prefs as unknown as Record<string, boolean>)) {
|
|
92
|
+
// User has disabled this notification type — mark sent to clear queue
|
|
93
|
+
this.queue.markSent(notif.id);
|
|
94
|
+
processed++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Render the template
|
|
100
|
+
const rendered = renderNotificationTemplate(notif.template as TemplateName, data);
|
|
101
|
+
|
|
102
|
+
// Send via email client
|
|
103
|
+
await this.emailClient.send({
|
|
104
|
+
to: email,
|
|
105
|
+
subject: rendered.subject,
|
|
106
|
+
html: rendered.html,
|
|
107
|
+
text: rendered.text,
|
|
108
|
+
userId: notif.tenantId,
|
|
109
|
+
templateName: notif.template,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.queue.markSent(notif.id);
|
|
113
|
+
processed++;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
logger.error("Notification send failed", {
|
|
116
|
+
notificationId: notif.id,
|
|
117
|
+
template: notif.template,
|
|
118
|
+
error: err instanceof Error ? err.message : String(err),
|
|
119
|
+
});
|
|
120
|
+
this.queue.markFailed(notif.id, notif.attempts + 1);
|
|
121
|
+
processed++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return processed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private isEnabledByPreferences(template: string, prefs: Record<string, boolean>): boolean {
|
|
129
|
+
const prefKey = PREF_MAP[template];
|
|
130
|
+
if (!prefKey) return true; // unknown template -> send by default
|
|
131
|
+
return prefs[prefKey] !== false; // default to enabled if key missing
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Verification Middleware — Blocks actions until email is verified.
|
|
3
|
+
*
|
|
4
|
+
* When used after session auth middleware, checks that the authenticated user
|
|
5
|
+
* has verified their email. API token auth (machine-to-machine) is not subject
|
|
6
|
+
* to email verification and is always allowed through.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Context, Next } from "hono";
|
|
10
|
+
import { logger } from "../config/logger.js";
|
|
11
|
+
|
|
12
|
+
/** Minimal interface for checking email verification status. */
|
|
13
|
+
export interface IEmailVerifier {
|
|
14
|
+
isVerified(userId: string): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create middleware that blocks session-authenticated users who haven't verified their email.
|
|
19
|
+
*
|
|
20
|
+
* API token auth (authMethod === "api_key") bypasses this check since machine
|
|
21
|
+
* clients don't have email addresses to verify.
|
|
22
|
+
*
|
|
23
|
+
* @param verifier - Email verification store
|
|
24
|
+
*/
|
|
25
|
+
export function requireEmailVerified(verifier: IEmailVerifier) {
|
|
26
|
+
return async (c: Context, next: Next) => {
|
|
27
|
+
let authMethod: string | undefined;
|
|
28
|
+
let userId: string | undefined;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
authMethod = c.get("authMethod");
|
|
32
|
+
const user = c.get("user") as { id: string } | undefined;
|
|
33
|
+
userId = user?.id;
|
|
34
|
+
} catch {
|
|
35
|
+
// No auth context set — let downstream auth middleware handle 401
|
|
36
|
+
return next();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// API token auth bypasses email verification
|
|
40
|
+
if (authMethod === "api_key") {
|
|
41
|
+
return next();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Session auth requires verified email
|
|
45
|
+
if (authMethod === "session" && userId) {
|
|
46
|
+
try {
|
|
47
|
+
if (!(await verifier.isVerified(userId))) {
|
|
48
|
+
return c.json(
|
|
49
|
+
{
|
|
50
|
+
error: "Email verification required",
|
|
51
|
+
message: "Please verify your email address before creating bots",
|
|
52
|
+
code: "EMAIL_NOT_VERIFIED",
|
|
53
|
+
},
|
|
54
|
+
403,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// If we can't check verification (DB issue), don't block the user
|
|
59
|
+
logger.warn("Email verification check failed", { error });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return next();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
type EmailOptions,
|
|
4
|
+
escapeHtml,
|
|
5
|
+
passwordResetTemplate,
|
|
6
|
+
passwordResetText,
|
|
7
|
+
sendEmail,
|
|
8
|
+
} from "./resend-adapter.js";
|
|
9
|
+
|
|
10
|
+
// Create a mock send function that can be controlled in tests
|
|
11
|
+
const mockSend = vi.fn();
|
|
12
|
+
|
|
13
|
+
// Mock the Resend module
|
|
14
|
+
vi.mock("resend", () => {
|
|
15
|
+
return {
|
|
16
|
+
Resend: class MockResend {
|
|
17
|
+
emails = {
|
|
18
|
+
send: mockSend,
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("escapeHtml", () => {
|
|
25
|
+
it("should escape all dangerous HTML characters", () => {
|
|
26
|
+
expect(escapeHtml("<script>alert('XSS')</script>")).toBe("<script>alert('XSS')</script>");
|
|
27
|
+
expect(escapeHtml('Test "quotes" & <tags>')).toBe("Test "quotes" & <tags>");
|
|
28
|
+
expect(escapeHtml("user@example.com")).toBe("user@example.com");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should handle empty strings", () => {
|
|
32
|
+
expect(escapeHtml("")).toBe("");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should handle strings with no special characters", () => {
|
|
36
|
+
expect(escapeHtml("normal text 123")).toBe("normal text 123");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("sendEmail", () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
delete process.env.RESEND_API_KEY;
|
|
44
|
+
delete process.env.RESEND_FROM_EMAIL;
|
|
45
|
+
// Reset mock to default success response
|
|
46
|
+
mockSend.mockResolvedValue({
|
|
47
|
+
data: { id: "test-email-id" },
|
|
48
|
+
error: null,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should send email with required options", async () => {
|
|
53
|
+
process.env.RESEND_API_KEY = "test-api-key";
|
|
54
|
+
|
|
55
|
+
const options: EmailOptions = {
|
|
56
|
+
to: "user@example.com",
|
|
57
|
+
subject: "Test Subject",
|
|
58
|
+
html: "<p>Test HTML</p>",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const result = await sendEmail(options);
|
|
62
|
+
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
id: "test-email-id",
|
|
65
|
+
success: true,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should use custom API key when provided", async () => {
|
|
70
|
+
const options: EmailOptions = {
|
|
71
|
+
to: "user@example.com",
|
|
72
|
+
subject: "Test Subject",
|
|
73
|
+
html: "<p>Test HTML</p>",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = await sendEmail(options, "custom-api-key");
|
|
77
|
+
|
|
78
|
+
expect(result.success).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should use custom from address when provided", async () => {
|
|
82
|
+
process.env.RESEND_API_KEY = "test-api-key";
|
|
83
|
+
|
|
84
|
+
const options: EmailOptions = {
|
|
85
|
+
to: "user@example.com",
|
|
86
|
+
subject: "Test Subject",
|
|
87
|
+
html: "<p>Test HTML</p>",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const result = await sendEmail(options, undefined, "custom@example.com");
|
|
91
|
+
|
|
92
|
+
expect(result.success).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should throw error when RESEND_API_KEY is missing", async () => {
|
|
96
|
+
const options: EmailOptions = {
|
|
97
|
+
to: "user@example.com",
|
|
98
|
+
subject: "Test Subject",
|
|
99
|
+
html: "<p>Test HTML</p>",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await expect(sendEmail(options)).rejects.toThrow("RESEND_API_KEY environment variable is required");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should handle Resend API errors", async () => {
|
|
106
|
+
process.env.RESEND_API_KEY = "test-api-key";
|
|
107
|
+
|
|
108
|
+
// Mock Resend to return an error for this specific test
|
|
109
|
+
mockSend.mockResolvedValueOnce({
|
|
110
|
+
data: null,
|
|
111
|
+
error: { message: "API error" },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const options: EmailOptions = {
|
|
115
|
+
to: "user@example.com",
|
|
116
|
+
subject: "Test Subject",
|
|
117
|
+
html: "<p>Test HTML</p>",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
await expect(sendEmail(options)).rejects.toThrow("Failed to send email: API error");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should use default from email when env var is set", async () => {
|
|
124
|
+
process.env.RESEND_API_KEY = "test-api-key";
|
|
125
|
+
process.env.RESEND_FROM_EMAIL = "default@example.com";
|
|
126
|
+
|
|
127
|
+
const options: EmailOptions = {
|
|
128
|
+
to: "user@example.com",
|
|
129
|
+
subject: "Test Subject",
|
|
130
|
+
html: "<p>Test HTML</p>",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const result = await sendEmail(options);
|
|
134
|
+
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should include text version when provided", async () => {
|
|
139
|
+
process.env.RESEND_API_KEY = "test-api-key";
|
|
140
|
+
|
|
141
|
+
const options: EmailOptions = {
|
|
142
|
+
to: "user@example.com",
|
|
143
|
+
subject: "Test Subject",
|
|
144
|
+
html: "<p>Test HTML</p>",
|
|
145
|
+
text: "Test plain text",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const result = await sendEmail(options);
|
|
149
|
+
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("passwordResetTemplate", () => {
|
|
155
|
+
it("should generate HTML template with reset URL", () => {
|
|
156
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
157
|
+
const email = "user@example.com";
|
|
158
|
+
|
|
159
|
+
const html = passwordResetTemplate(resetUrl, email);
|
|
160
|
+
|
|
161
|
+
expect(html).toContain(resetUrl);
|
|
162
|
+
expect(html).toContain(email);
|
|
163
|
+
expect(html).toContain("Reset Your Password");
|
|
164
|
+
expect(html).toContain("Reset Password");
|
|
165
|
+
expect(html).toContain("<!DOCTYPE html>");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should escape HTML characters in email and URL to prevent XSS", () => {
|
|
169
|
+
const resetUrl = "https://wopr.bot/reset?token=<script>alert(1)</script>";
|
|
170
|
+
const email = "<script>alert('xss')</script>@example.com";
|
|
171
|
+
|
|
172
|
+
const html = passwordResetTemplate(resetUrl, email);
|
|
173
|
+
|
|
174
|
+
// Verify email is escaped in the text where it's displayed
|
|
175
|
+
expect(html).toContain("<script>alert('xss')</script>@example.com");
|
|
176
|
+
|
|
177
|
+
// Verify resetUrl is escaped in the displayed text (not href)
|
|
178
|
+
expect(html).toContain("<script>alert(1)</script>");
|
|
179
|
+
|
|
180
|
+
// Verify the email script tag doesn't appear unescaped in body text
|
|
181
|
+
// (it may appear in href attribute, which is safe from XSS)
|
|
182
|
+
const bodyMatch = html.match(/<p>.*<\/p>/gs);
|
|
183
|
+
const bodyText = bodyMatch ? bodyMatch.join("") : "";
|
|
184
|
+
expect(bodyText).not.toContain("<script>alert('xss')</script>");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should include current year in footer", () => {
|
|
188
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
189
|
+
const email = "user@example.com";
|
|
190
|
+
|
|
191
|
+
const html = passwordResetTemplate(resetUrl, email);
|
|
192
|
+
|
|
193
|
+
const currentYear = new Date().getFullYear();
|
|
194
|
+
expect(html).toContain(`© ${currentYear} WOPR Network`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should be valid HTML structure", () => {
|
|
198
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
199
|
+
const email = "user@example.com";
|
|
200
|
+
|
|
201
|
+
const html = passwordResetTemplate(resetUrl, email);
|
|
202
|
+
|
|
203
|
+
expect(html).toContain("<html>");
|
|
204
|
+
expect(html).toContain("</html>");
|
|
205
|
+
expect(html).toContain("<head>");
|
|
206
|
+
expect(html).toContain("</head>");
|
|
207
|
+
expect(html).toContain("<body");
|
|
208
|
+
expect(html).toContain("</body>");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("passwordResetText", () => {
|
|
213
|
+
it("should generate plain text with reset URL", () => {
|
|
214
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
215
|
+
const email = "user@example.com";
|
|
216
|
+
|
|
217
|
+
const text = passwordResetText(resetUrl, email);
|
|
218
|
+
|
|
219
|
+
expect(text).toContain(resetUrl);
|
|
220
|
+
expect(text).toContain(email);
|
|
221
|
+
expect(text).toContain("Reset Your Password");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should not contain HTML tags", () => {
|
|
225
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
226
|
+
const email = "user@example.com";
|
|
227
|
+
|
|
228
|
+
const text = passwordResetText(resetUrl, email);
|
|
229
|
+
|
|
230
|
+
expect(text).not.toContain("<");
|
|
231
|
+
expect(text).not.toContain(">");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should include current year in footer", () => {
|
|
235
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
236
|
+
const email = "user@example.com";
|
|
237
|
+
|
|
238
|
+
const text = passwordResetText(resetUrl, email);
|
|
239
|
+
|
|
240
|
+
const currentYear = new Date().getFullYear();
|
|
241
|
+
expect(text).toContain(`© ${currentYear} WOPR Network`);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should be readable plain text format", () => {
|
|
245
|
+
const resetUrl = "https://wopr.bot/reset?token=abc123";
|
|
246
|
+
const email = "user@example.com";
|
|
247
|
+
|
|
248
|
+
const text = passwordResetText(resetUrl, email);
|
|
249
|
+
|
|
250
|
+
// Should have line breaks and be formatted
|
|
251
|
+
expect(text.split("\n").length).toBeGreaterThan(5);
|
|
252
|
+
});
|
|
253
|
+
});
|