payment-kit 1.29.1 → 1.29.2
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/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +81 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +50 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +259 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +214 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +17 -12
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +12 -4
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +667 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +66 -22
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +236 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +10 -56
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Stripped LOCAL-ONLY config for the express→hono Phase 4 CF runtime E2E.
|
|
3
|
+
// Drops the external service bindings (AUTH_SERVICE / MEDIA_KIT — separate
|
|
4
|
+
// workers not in this repo), HYPERDRIVE, queues, assets, and crons so the worker
|
|
5
|
+
// boots under `wrangler dev` with just a local D1 + KV. Auth is therefore absent
|
|
6
|
+
// (caller resolves to null) — only PUBLIC read routes + the 404 path are testable.
|
|
7
|
+
"name": "payment-kit-local-e2e",
|
|
8
|
+
"main": "dist/worker.js",
|
|
9
|
+
"compatibility_date": "2024-12-01",
|
|
10
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
11
|
+
"d1_databases": [
|
|
12
|
+
{
|
|
13
|
+
"binding": "DB",
|
|
14
|
+
"database_name": "payment-kit-prod",
|
|
15
|
+
"database_id": "ea6c75d0-39b8-40cd-a0ae-a2062e77c4b9",
|
|
16
|
+
"migrations_dir": "migrations"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"kv_namespaces": [{ "binding": "DID_CONNECT_KV", "id": "local-e2e-kv" }],
|
|
20
|
+
"vars": {
|
|
21
|
+
"APP_NAME": "Payment Kit",
|
|
22
|
+
"APP_PID": "payment-kit-dev",
|
|
23
|
+
"APP_URL": "http://localhost:8787",
|
|
24
|
+
"PAYMENT_LIVEMODE": "false"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/jest.config.js
CHANGED
|
@@ -13,7 +13,9 @@ module.exports = {
|
|
|
13
13
|
'^.+\\.js$': ['babel-jest', { presets: [['@babel/preset-env', { targets: { node: 'current' } }]] }],
|
|
14
14
|
},
|
|
15
15
|
transformIgnorePatterns: [
|
|
16
|
-
|
|
16
|
+
// `validator` ships an ESM build under validator/es/* that resource routes
|
|
17
|
+
// (customers) import directly; transform it so jest can load those routes.
|
|
18
|
+
'node_modules/(?!.*(@noble|@scure|string-width|strip-ansi|ansi-regex|p-retry|is-network-error|validator|uuid)/)',
|
|
17
19
|
],
|
|
18
20
|
testMatch: ['**/tests/**/*.spec.ts'],
|
|
19
21
|
collectCoverageFrom: ['api/src/**/*.ts'],
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.2",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"prelint": "npm run types",
|
|
7
|
-
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
7
|
+
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx && node scripts/scan-tenant-queries.js && node scripts/scan-core-env.js",
|
|
8
8
|
"lint:fix": "pnpm run lint --fix",
|
|
9
9
|
"format": "prettier -w src",
|
|
10
10
|
"test": "node scripts/jest.js",
|
|
@@ -46,60 +46,55 @@
|
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@abtnode/cron": "^1.17.
|
|
49
|
+
"@abtnode/cron": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
50
50
|
"@apple/app-store-server-library": "^3.1.0",
|
|
51
|
-
"@arcblock/did": "^1.30.
|
|
52
|
-
"@arcblock/did-connect-js": "4.0.
|
|
53
|
-
"@arcblock/did-connect-react": "^3.5.
|
|
51
|
+
"@arcblock/did": "^1.30.24",
|
|
52
|
+
"@arcblock/did-connect-js": "4.0.1-beta.6",
|
|
53
|
+
"@arcblock/did-connect-react": "^3.5.4",
|
|
54
54
|
"@arcblock/did-connect-storage-nedb": "^1.8.0",
|
|
55
|
-
"@arcblock/did-util": "^1.30.
|
|
56
|
-
"@arcblock/jwt": "^1.30.
|
|
57
|
-
"@arcblock/react-hooks": "^3.5.
|
|
58
|
-
"@arcblock/ux": "^3.5.
|
|
59
|
-
"@arcblock/validator": "^1.30.
|
|
60
|
-
"@arcblock/vc": "^1.30.
|
|
61
|
-
"@blocklet/did-space-js": "^1.2.23",
|
|
55
|
+
"@arcblock/did-util": "^1.30.24",
|
|
56
|
+
"@arcblock/jwt": "^1.30.24",
|
|
57
|
+
"@arcblock/react-hooks": "^3.5.4",
|
|
58
|
+
"@arcblock/ux": "^3.5.4",
|
|
59
|
+
"@arcblock/validator": "^1.30.24",
|
|
60
|
+
"@arcblock/vc": "^1.30.24",
|
|
62
61
|
"@blocklet/error": "^0.3.5",
|
|
63
|
-
"@blocklet/js-sdk": "^1.17.
|
|
64
|
-
"@blocklet/logger": "^1.17.
|
|
65
|
-
"@blocklet/payment-broker-client": "1.29.
|
|
66
|
-
"@blocklet/payment-react": "1.29.
|
|
67
|
-
"@blocklet/payment-vendor": "1.29.
|
|
68
|
-
"@blocklet/sdk": "^1.17.
|
|
69
|
-
"@blocklet/ui-react": "^3.5.
|
|
62
|
+
"@blocklet/js-sdk": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
63
|
+
"@blocklet/logger": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
64
|
+
"@blocklet/payment-broker-client": "1.29.2",
|
|
65
|
+
"@blocklet/payment-react": "1.29.2",
|
|
66
|
+
"@blocklet/payment-vendor": "1.29.2",
|
|
67
|
+
"@blocklet/sdk": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
68
|
+
"@blocklet/ui-react": "^3.5.4",
|
|
70
69
|
"@blocklet/uploader": "^0.3.20",
|
|
71
70
|
"@blocklet/xss": "^0.3.16",
|
|
71
|
+
"@hono/node-server": "2.0.4",
|
|
72
72
|
"@mui/icons-material": "^7.1.2",
|
|
73
73
|
"@mui/lab": "7.0.0-beta.14",
|
|
74
74
|
"@mui/material": "^7.1.2",
|
|
75
75
|
"@mui/system": "^7.1.1",
|
|
76
|
-
"@ocap/asset": "^1.30.
|
|
77
|
-
"@ocap/client": "^1.30.
|
|
78
|
-
"@ocap/mcrypto": "^1.30.
|
|
79
|
-
"@ocap/util": "^1.30.
|
|
80
|
-
"@ocap/wallet": "^1.30.
|
|
76
|
+
"@ocap/asset": "^1.30.24",
|
|
77
|
+
"@ocap/client": "^1.30.24",
|
|
78
|
+
"@ocap/mcrypto": "^1.30.24",
|
|
79
|
+
"@ocap/util": "^1.30.24",
|
|
80
|
+
"@ocap/wallet": "^1.30.24",
|
|
81
81
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
82
82
|
"@stripe/stripe-js": "^2.4.0",
|
|
83
83
|
"ahooks": "^3.8.5",
|
|
84
84
|
"axios": "^1.15.2",
|
|
85
85
|
"bignumber.js": "^9.3.1",
|
|
86
|
-
"body-parser": "^1.20.3",
|
|
87
86
|
"cls-hooked": "^4.2.2",
|
|
88
|
-
"cookie-parser": "^1.4.7",
|
|
89
87
|
"copy-to-clipboard": "^3.3.3",
|
|
90
|
-
"cors": "^2.8.5",
|
|
91
88
|
"date-fns": "^3.6.0",
|
|
92
89
|
"dayjs": "^1.11.13",
|
|
93
90
|
"debug": "^4.4.1",
|
|
94
91
|
"dotenv-flow": "^3.3.0",
|
|
95
92
|
"ethers": "^6.14.4",
|
|
96
|
-
"express": "^4.21.2",
|
|
97
|
-
"express-async-errors": "^3.1.1",
|
|
98
|
-
"express-history-api-fallback": "^2.2.1",
|
|
99
93
|
"fastq": "^1.19.1",
|
|
100
94
|
"flat": "^5.0.2",
|
|
101
95
|
"google-libphonenumber": "^3.2.42",
|
|
102
96
|
"google-play-billing-validator": "^2.1.3",
|
|
97
|
+
"hono": "4.12.12",
|
|
103
98
|
"html2canvas": "^1.4.1",
|
|
104
99
|
"iframe-resizer-react": "^1.1.1",
|
|
105
100
|
"joi": "17.12.2",
|
|
@@ -125,7 +120,7 @@
|
|
|
125
120
|
"react-router-dom": "^6.30.3",
|
|
126
121
|
"recharts": "^2.15.4",
|
|
127
122
|
"rimraf": "^3.0.2",
|
|
128
|
-
"sequelize": "
|
|
123
|
+
"sequelize": "~6.37.8",
|
|
129
124
|
"sql-where-parser": "^2.2.1",
|
|
130
125
|
"sqlite3": "^5.1.7",
|
|
131
126
|
"stripe": "^13.11.0",
|
|
@@ -138,14 +133,12 @@
|
|
|
138
133
|
"web3": "^4.16.0"
|
|
139
134
|
},
|
|
140
135
|
"devDependencies": {
|
|
141
|
-
"@abtnode/types": "^1.17.
|
|
136
|
+
"@abtnode/types": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
142
137
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
143
|
-
"@blocklet/payment-types": "1.29.
|
|
144
|
-
"@types/
|
|
145
|
-
"@types/cors": "^2.8.19",
|
|
138
|
+
"@blocklet/payment-types": "1.29.2",
|
|
139
|
+
"@types/connect": "^3.4.38",
|
|
146
140
|
"@types/debug": "^4.1.12",
|
|
147
141
|
"@types/dotenv-flow": "^3.3.3",
|
|
148
|
-
"@types/express": "^4.17.23",
|
|
149
142
|
"@types/node": "^18.19.112",
|
|
150
143
|
"@types/node-apple-receipt-verify": "^1.7.5",
|
|
151
144
|
"@types/react": "^18.3.23",
|
|
@@ -154,6 +147,7 @@
|
|
|
154
147
|
"babel-plugin-lodash": "^3.3.4",
|
|
155
148
|
"bumpp": "^8.2.1",
|
|
156
149
|
"code-inspector-plugin": "^1.2.3",
|
|
150
|
+
"connect": "^3.7.0",
|
|
157
151
|
"cross-env": "^7.0.3",
|
|
158
152
|
"eslint": "^8.57.1",
|
|
159
153
|
"import-sort-style-module": "^6.0.0",
|
|
@@ -176,6 +170,7 @@
|
|
|
176
170
|
"vite-plugin-node-polyfills": "^0.23.0",
|
|
177
171
|
"vite-plugin-svgr": "^4.3.0",
|
|
178
172
|
"vite-tsconfig-paths": "^5.1.4",
|
|
173
|
+
"wrangler": "^4.100.0",
|
|
179
174
|
"zx": "^8.8.5"
|
|
180
175
|
},
|
|
181
176
|
"importSort": {
|
|
@@ -188,5 +183,5 @@
|
|
|
188
183
|
"parser": "typescript"
|
|
189
184
|
}
|
|
190
185
|
},
|
|
191
|
-
"gitHead": "
|
|
186
|
+
"gitHead": "b4f9a33207cb2341638efff848a900087a18e6cf"
|
|
192
187
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Phase 12b E2E harness — drives the core queue RUNTIME surface against a real
|
|
2
|
+
// sqlite DB (outside jest) and prints JSON-shaped results for the build-phases
|
|
3
|
+
// E2E hard gate. Proves worker(workerd) ↔ node parity for delay/cancel and that
|
|
4
|
+
// the host-driven dispatchDueJobs()/flushQueueWork() seam runs the SAME engine.
|
|
5
|
+
//
|
|
6
|
+
// Run: npx tsx scripts/e2e-12b-runtime.ts (cwd = blocklets/core)
|
|
7
|
+
/* eslint-disable no-console, global-require, @typescript-eslint/no-var-requires */
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { Sequelize } from 'sequelize';
|
|
12
|
+
import { SequelizeStorage, Umzug } from 'umzug';
|
|
13
|
+
|
|
14
|
+
const TENANT_A = 'did:abt:zE2E12BA';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const STORE_DIR = path.join(process.cwd(), 'api/src/store');
|
|
18
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'e2e-12b-'));
|
|
19
|
+
// store/migrate.ts eagerly builds a sequelize at module-load (needs a data
|
|
20
|
+
// dir). We bind models to our OWN sqlite below; this just satisfies the eager
|
|
21
|
+
// path. Set BEFORE any in-repo require.
|
|
22
|
+
process.env.BLOCKLET_DATA_DIR = dir;
|
|
23
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
24
|
+
// single-mode default tenant for the tenant-backfill migration + config gate
|
|
25
|
+
process.env.BLOCKLET_APP_PID = process.env.BLOCKLET_APP_PID || TENANT_A;
|
|
26
|
+
process.env.BLOCKLET_APP_ID = process.env.BLOCKLET_APP_ID || TENANT_A;
|
|
27
|
+
const sequelize = new Sequelize({ dialect: 'sqlite', storage: path.join(dir, 'e2e.db'), logging: false });
|
|
28
|
+
const qi = sequelize.getQueryInterface();
|
|
29
|
+
const migDir = path.join(STORE_DIR, 'migrations');
|
|
30
|
+
const migrations = fs
|
|
31
|
+
.readdirSync(migDir)
|
|
32
|
+
.filter((f) => f.endsWith('.ts'))
|
|
33
|
+
.sort()
|
|
34
|
+
.map((f) => {
|
|
35
|
+
const mod = require(path.join(migDir, f));
|
|
36
|
+
return { name: f, up: async () => mod.up({ context: qi }), down: async () => mod.down({ context: qi }) };
|
|
37
|
+
});
|
|
38
|
+
const umzug = new Umzug({ migrations, storage: new SequelizeStorage({ sequelize }), logger: undefined });
|
|
39
|
+
await umzug.up();
|
|
40
|
+
require('../api/src/store/models').initialize(sequelize);
|
|
41
|
+
|
|
42
|
+
const createQueue = require('../api/src/libs/queue').default;
|
|
43
|
+
const {
|
|
44
|
+
setQueueRuntimeMode,
|
|
45
|
+
getAllQueueNames,
|
|
46
|
+
dispatchDueJobs,
|
|
47
|
+
flushQueueWork,
|
|
48
|
+
} = require('../api/src/libs/queue/runtime');
|
|
49
|
+
|
|
50
|
+
const out: any[] = [];
|
|
51
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
52
|
+
|
|
53
|
+
// --- S12b.1 registry non-empty (canonical engine, not the dead shim) ---
|
|
54
|
+
setQueueRuntimeMode('workerd');
|
|
55
|
+
let ranDue = 0;
|
|
56
|
+
const q = createQueue({
|
|
57
|
+
name: 'e2e-due',
|
|
58
|
+
onJob: async () => {
|
|
59
|
+
ranDue += 1;
|
|
60
|
+
},
|
|
61
|
+
options: { enableScheduledJob: true },
|
|
62
|
+
});
|
|
63
|
+
out.push({ case: 'S12b.1 registry non-empty', input: 'createQueue(e2e-due)', output: { queues: getAllQueueNames() } });
|
|
64
|
+
|
|
65
|
+
// --- S12b.2 workerd disables the background loop (no auto-dispatch) ---
|
|
66
|
+
await q.store.addJob('due-1', { v: 1, instance_did: TENANT_A }, { delay: 5, will_run_at: Date.now() - 1000 });
|
|
67
|
+
await sleep(80);
|
|
68
|
+
out.push({ case: 'S12b.2 workerd loop disabled', input: 'due delayed row + wait 80ms', output: { ranBeforeDispatch: ranDue } });
|
|
69
|
+
|
|
70
|
+
// --- S12b.3 host-driven dispatchDueJobs executes the due delayed job once ---
|
|
71
|
+
const dispatchResult = await dispatchDueJobs();
|
|
72
|
+
await flushQueueWork();
|
|
73
|
+
out.push({
|
|
74
|
+
case: 'S12b.3 dispatchDueJobs executes due job once',
|
|
75
|
+
input: 'dispatchDueJobs() + flushQueueWork()',
|
|
76
|
+
output: { dispatched: dispatchResult.dispatched, ran: ranDue, rowCleared: (await q.store.getJob('due-1')) === null },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// --- S12b.4 NEGATIVE: cancel an in-flight delayed job — no half-execution ---
|
|
80
|
+
let ranCancel = 0;
|
|
81
|
+
const qc = createQueue({
|
|
82
|
+
name: 'e2e-cancel',
|
|
83
|
+
onJob: async () => {
|
|
84
|
+
ranCancel += 1;
|
|
85
|
+
},
|
|
86
|
+
options: { enableScheduledJob: true },
|
|
87
|
+
});
|
|
88
|
+
await qc.store.addJob('cancel-1', { v: 1, instance_did: TENANT_A }, { delay: 5, will_run_at: Date.now() - 1000 });
|
|
89
|
+
await qc.cancel('cancel-1');
|
|
90
|
+
const cancelDispatch = await dispatchDueJobs();
|
|
91
|
+
await flushQueueWork();
|
|
92
|
+
out.push({
|
|
93
|
+
case: 'S12b.4 cancel in-flight leaves no residue',
|
|
94
|
+
input: 'cancel(cancel-1) before dispatchDueJobs()',
|
|
95
|
+
output: { dispatched: cancelDispatch.dispatched, ran: ranCancel },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// --- S12b.5 node entry parity: same redispatchDue body runs on a node loop ---
|
|
99
|
+
setQueueRuntimeMode('node');
|
|
100
|
+
let ranNode = 0;
|
|
101
|
+
const qn = createQueue({
|
|
102
|
+
name: 'e2e-node',
|
|
103
|
+
onJob: async () => {
|
|
104
|
+
ranNode += 1;
|
|
105
|
+
},
|
|
106
|
+
options: { enableScheduledJob: true, retryDelay: 1 },
|
|
107
|
+
});
|
|
108
|
+
await qn.store.addJob('node-1', { v: 1, instance_did: TENANT_A }, { delay: 2, will_run_at: Date.now() - 1000 });
|
|
109
|
+
// node loop polls every minDelay/2 (~1s in test) — wait for one cycle
|
|
110
|
+
await sleep(1500);
|
|
111
|
+
out.push({
|
|
112
|
+
case: 'S12b.5 node loop dispatches the same way',
|
|
113
|
+
input: 'node-mode delayed row + wait one poll cycle',
|
|
114
|
+
output: { ran: ranNode, rowCleared: (await qn.store.getJob('node-1')) === null },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// --- S12b.6 retry parity (node engine): maxRetries attempts then failed ---
|
|
118
|
+
let attempts = 0;
|
|
119
|
+
const qr = createQueue({
|
|
120
|
+
name: 'e2e-retry',
|
|
121
|
+
onJob: async () => {
|
|
122
|
+
attempts += 1;
|
|
123
|
+
throw new Error('always');
|
|
124
|
+
},
|
|
125
|
+
options: { maxRetries: 3, retryDelay: 1 },
|
|
126
|
+
});
|
|
127
|
+
const settled: any = await new Promise((resolve) => {
|
|
128
|
+
const ev = qr.push({ job: { v: 1, instance_did: TENANT_A } });
|
|
129
|
+
ev.on('failed', (d: any) => resolve({ event: 'failed', id: d.id }));
|
|
130
|
+
ev.on('finished', (d: any) => resolve({ event: 'finished', id: d.id }));
|
|
131
|
+
});
|
|
132
|
+
out.push({
|
|
133
|
+
case: 'S12b.6 retry up to maxRetries then failed',
|
|
134
|
+
input: 'maxRetries:3 onJob always throws',
|
|
135
|
+
output: { event: settled.event, attempts },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(JSON.stringify({ phase: '12b', success: true, cases: out }, null, 2));
|
|
139
|
+
|
|
140
|
+
await sequelize.close();
|
|
141
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main().catch((err) => {
|
|
146
|
+
console.log(JSON.stringify({ phase: '12b', success: false, error: err?.message || String(err) }));
|
|
147
|
+
console.error(err?.stack || err);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// Phase 8 (W2′) deterministic E2E: prove the injected config slot is authoritative
|
|
3
|
+
// and fails closed on a missing required field. Emits raw JSON for each call.
|
|
4
|
+
//
|
|
5
|
+
// npx tsx scripts/e2e-core-config.ts
|
|
6
|
+
|
|
7
|
+
import { setCoreConfig, readConfig } from '../api/src/libs/env';
|
|
8
|
+
import { getTenantMode, getDefaultInstanceDid, setDefaultInstanceDid } from '../api/src/libs/tenant';
|
|
9
|
+
import { createEmbeddedPaymentService } from '../api/src/service';
|
|
10
|
+
|
|
11
|
+
function emit(label: string, input: any, output: any) {
|
|
12
|
+
console.log(`=== ${label} ===`);
|
|
13
|
+
console.log(`input: ${JSON.stringify(input)}`);
|
|
14
|
+
console.log(`output: ${JSON.stringify(output)}`);
|
|
15
|
+
console.log('');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function tryFactory(slots: any): { threw: boolean; name?: string; code?: string; field?: string; message?: string } {
|
|
19
|
+
try {
|
|
20
|
+
createEmbeddedPaymentService(slots);
|
|
21
|
+
return { threw: false };
|
|
22
|
+
} catch (err: any) {
|
|
23
|
+
return { threw: true, name: err?.name, code: err?.code, field: err?.field, message: err?.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
const checks: Array<[string, boolean]> = [];
|
|
29
|
+
|
|
30
|
+
// ---- Happy: injected config is authoritative, env is the fallback ----
|
|
31
|
+
process.env.E2E_KEY = 'from-env';
|
|
32
|
+
setCoreConfig({ E2E_KEY: 'from-config', PAYMENT_TENANT_MODE: 'multi' });
|
|
33
|
+
const injected = readConfig('E2E_KEY');
|
|
34
|
+
emit('S8.1 injected config wins over process.env', { env: 'from-env', config: 'from-config' }, { read: injected });
|
|
35
|
+
checks.push(['injected config wins', injected === 'from-config']);
|
|
36
|
+
|
|
37
|
+
setCoreConfig({ ONLY: 'x' });
|
|
38
|
+
const fallback = readConfig('E2E_KEY');
|
|
39
|
+
emit('S8.2 fallback to process.env when key absent from config', { configKeys: ['ONLY'] }, { read: fallback });
|
|
40
|
+
checks.push(['fallback to env', fallback === 'from-env']);
|
|
41
|
+
delete process.env.E2E_KEY;
|
|
42
|
+
|
|
43
|
+
// ---- getTenantMode reads mode from injected config (single source of truth) ----
|
|
44
|
+
delete process.env.PAYMENT_TENANT_MODE;
|
|
45
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'multi' });
|
|
46
|
+
const modeMulti = getTenantMode();
|
|
47
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'single' });
|
|
48
|
+
const modeSingle = getTenantMode();
|
|
49
|
+
emit('S8.3 getTenantMode from injected config', { config: ['multi', 'single'] }, { modeMulti, modeSingle });
|
|
50
|
+
checks.push(['mode multi from config', modeMulti === 'multi']);
|
|
51
|
+
checks.push(['mode single from config', modeSingle === 'single']);
|
|
52
|
+
|
|
53
|
+
// injected config wins over a conflicting env mirror
|
|
54
|
+
process.env.PAYMENT_TENANT_MODE = 'single';
|
|
55
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'multi' });
|
|
56
|
+
const modeConflict = getTenantMode();
|
|
57
|
+
emit('S8.4 config wins over conflicting process.env', { env: 'single', config: 'multi' }, { mode: modeConflict });
|
|
58
|
+
checks.push(['config beats conflicting env', modeConflict === 'multi']);
|
|
59
|
+
delete process.env.PAYMENT_TENANT_MODE;
|
|
60
|
+
|
|
61
|
+
// app DID from injected config
|
|
62
|
+
setDefaultInstanceDid(undefined);
|
|
63
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
64
|
+
setCoreConfig({ BLOCKLET_APP_PID: 'did:abt:zCONFIGAPP', PAYMENT_TENANT_MODE: 'single' });
|
|
65
|
+
const appDid = getDefaultInstanceDid();
|
|
66
|
+
emit('S8.5 getDefaultInstanceDid from injected config', { config: 'did:abt:zCONFIGAPP' }, { appDid });
|
|
67
|
+
checks.push(['app DID from config', appDid === 'did:abt:zCONFIGAPP']);
|
|
68
|
+
|
|
69
|
+
// ---- Negative: fail-fast on missing required field ----
|
|
70
|
+
setCoreConfig(undefined);
|
|
71
|
+
setDefaultInstanceDid(undefined);
|
|
72
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
73
|
+
const failFast = tryFactory({ config: {}, db: { sequelize: {} } });
|
|
74
|
+
emit('S8.6 single-mode factory missing BLOCKLET_APP_PID -> fail-fast', { config: {}, tenancy: 'single(default)' }, failFast);
|
|
75
|
+
checks.push([
|
|
76
|
+
'fail-fast MissingConfigError(BLOCKLET_APP_PID)',
|
|
77
|
+
failFast.threw && failFast.code === 'MISSING_CONFIG_FIELD' && failFast.field === 'BLOCKLET_APP_PID',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// multi mode does not require BLOCKLET_APP_PID (tenant resolved per request)
|
|
81
|
+
setCoreConfig(undefined);
|
|
82
|
+
const multiNoPid = tryFactory({ config: { PAYMENT_TENANT_MODE: 'multi' }, db: { sequelize: {} }, tenancy: { mode: 'multi' } });
|
|
83
|
+
emit('S8.7 multi mode does NOT require BLOCKLET_APP_PID', { tenancy: 'multi' }, multiNoPid);
|
|
84
|
+
checks.push(['multi mode skips the requirement', !(multiNoPid.code === 'MISSING_CONFIG_FIELD')]);
|
|
85
|
+
|
|
86
|
+
// ---- Layer 3: adversarial ----
|
|
87
|
+
console.log('##### LAYER 3: ADVERSARIAL #####\n');
|
|
88
|
+
// a) prototype-pollution key in config must not poison readConfig or globals
|
|
89
|
+
setCoreConfig(JSON.parse('{"__proto__": {"polluted": true}, "SAFE": "ok"}'));
|
|
90
|
+
const polluted = ({} as any).polluted === true;
|
|
91
|
+
const safeRead = readConfig('SAFE');
|
|
92
|
+
emit('S8.A1 prototype-pollution config key', { config: '{"__proto__":{"polluted":true},"SAFE":"ok"}' }, { globalPolluted: polluted, safeRead });
|
|
93
|
+
checks.push(['no prototype pollution from config', polluted === false]);
|
|
94
|
+
checks.push(['benign keys still readable', safeRead === 'ok']);
|
|
95
|
+
|
|
96
|
+
// b) the fail-fast error message must not echo secret values
|
|
97
|
+
setCoreConfig(undefined);
|
|
98
|
+
setDefaultInstanceDid(undefined);
|
|
99
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
100
|
+
const secretLeak = tryFactory({ config: { STRIPE_SECRET: 'sk_live_DEADBEEF_should_not_appear' }, db: { sequelize: {} } });
|
|
101
|
+
const leaks = (secretLeak.message || '').includes('sk_live_DEADBEEF');
|
|
102
|
+
emit('S8.A2 fail-fast error does not echo secret config values', { secretInConfig: 'sk_live_DEADBEEF...' }, { message: secretLeak.message, leaks });
|
|
103
|
+
checks.push(['error does not leak secret value', leaks === false]);
|
|
104
|
+
|
|
105
|
+
// c) numeric/garbage config coerced to string by the boundary (no crash)
|
|
106
|
+
setCoreConfig({ NUMERIC: 12345, BOOL: true });
|
|
107
|
+
const numeric = readConfig('NUMERIC');
|
|
108
|
+
const bool = readConfig('BOOL');
|
|
109
|
+
emit('S8.A3 non-string config values coerced safely', { NUMERIC: 12345, BOOL: true }, { numeric, bool });
|
|
110
|
+
checks.push(['non-string config coerced to string', numeric === '12345' && bool === 'true']);
|
|
111
|
+
|
|
112
|
+
// restore + report
|
|
113
|
+
setCoreConfig(undefined);
|
|
114
|
+
|
|
115
|
+
console.log('##### ASSERTIONS #####');
|
|
116
|
+
let allPass = true;
|
|
117
|
+
for (const [name, ok] of checks) {
|
|
118
|
+
if (!ok) allPass = false;
|
|
119
|
+
console.log(JSON.stringify({ check: name, pass: ok }));
|
|
120
|
+
}
|
|
121
|
+
console.log(JSON.stringify({ success: allPass, total: checks.length, passed: checks.filter(([, ok]) => ok).length }));
|
|
122
|
+
if (!allPass) process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// D1 (S3.0) E2E harness — really runs the embedded factory with the three input
|
|
3
|
+
// matrices and prints JSON-shaped results. Invoked by build-phases Layer 2; the
|
|
4
|
+
// raw output is captured into planning/.../logs/sD1-e2e.log.
|
|
5
|
+
//
|
|
6
|
+
// Run: npx tsx scripts/e2e-d1-tenancy.ts
|
|
7
|
+
import { Sequelize } from 'sequelize';
|
|
8
|
+
import { setCoreConfig, getCoreConfig } from '../api/src/libs/env';
|
|
9
|
+
import { getTenantMode, setDefaultInstanceDid } from '../api/src/libs/tenant';
|
|
10
|
+
import {
|
|
11
|
+
createDefaultIdentityDriver,
|
|
12
|
+
setIdentityDriver,
|
|
13
|
+
createDefaultSecretsDriver,
|
|
14
|
+
setSecretsDriver,
|
|
15
|
+
} from '../api/src/libs/drivers';
|
|
16
|
+
import { createEmbeddedPaymentService } from '../api/src/service';
|
|
17
|
+
|
|
18
|
+
const identity = { resolveInstanceDidForHost: () => null } as any;
|
|
19
|
+
const secrets = createDefaultSecretsDriver();
|
|
20
|
+
|
|
21
|
+
function freshDb() {
|
|
22
|
+
return { sequelize: new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) as any };
|
|
23
|
+
}
|
|
24
|
+
function reset() {
|
|
25
|
+
setCoreConfig(undefined);
|
|
26
|
+
setDefaultInstanceDid(undefined);
|
|
27
|
+
setIdentityDriver(createDefaultIdentityDriver());
|
|
28
|
+
setSecretsDriver(createDefaultSecretsDriver());
|
|
29
|
+
delete process.env.PAYMENT_TENANT_MODE;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emit(label: string, obj: unknown) {
|
|
33
|
+
console.log(`=== ${label} ===`);
|
|
34
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// matrix 1 — slot drives multi (no env)
|
|
38
|
+
reset();
|
|
39
|
+
createEmbeddedPaymentService({ config: {}, db: freshDb(), tenancy: { mode: 'multi' }, identity, secrets });
|
|
40
|
+
emit('M1 slot-multi-no-env', { tenantMode: getTenantMode(), success: getTenantMode() === 'multi' });
|
|
41
|
+
|
|
42
|
+
// matrix 2 — config carries multi, slot agrees
|
|
43
|
+
reset();
|
|
44
|
+
createEmbeddedPaymentService({
|
|
45
|
+
config: { PAYMENT_TENANT_MODE: 'multi' },
|
|
46
|
+
db: freshDb(),
|
|
47
|
+
tenancy: { mode: 'multi' },
|
|
48
|
+
identity,
|
|
49
|
+
secrets,
|
|
50
|
+
});
|
|
51
|
+
emit('M2 config-multi-slot-agrees', { tenantMode: getTenantMode(), success: getTenantMode() === 'multi' });
|
|
52
|
+
|
|
53
|
+
// matrix 3 (NEGATIVE) — config single conflicts with slot multi -> must throw, no mode written
|
|
54
|
+
reset();
|
|
55
|
+
let conflictErr: any = null;
|
|
56
|
+
try {
|
|
57
|
+
createEmbeddedPaymentService({
|
|
58
|
+
config: { PAYMENT_TENANT_MODE: 'single' },
|
|
59
|
+
db: freshDb(),
|
|
60
|
+
tenancy: { mode: 'multi' },
|
|
61
|
+
identity,
|
|
62
|
+
secrets,
|
|
63
|
+
});
|
|
64
|
+
} catch (err: any) {
|
|
65
|
+
conflictErr = err;
|
|
66
|
+
}
|
|
67
|
+
emit('M3 conflict-negative', {
|
|
68
|
+
threw: conflictErr !== null,
|
|
69
|
+
name: conflictErr?.name,
|
|
70
|
+
code: conflictErr?.code,
|
|
71
|
+
message: conflictErr?.message,
|
|
72
|
+
coreConfigAfter: getCoreConfig() === undefined ? 'undefined (no half write)' : 'WRITTEN (bug!)',
|
|
73
|
+
success: conflictErr !== null && getCoreConfig() === undefined,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// matrix 4 (NEGATIVE) — multi without secrets -> fail-closed (no silent single)
|
|
77
|
+
reset();
|
|
78
|
+
let slotErr: any = null;
|
|
79
|
+
try {
|
|
80
|
+
createEmbeddedPaymentService({ config: {}, db: freshDb(), tenancy: { mode: 'multi' }, identity });
|
|
81
|
+
} catch (err: any) {
|
|
82
|
+
slotErr = err;
|
|
83
|
+
}
|
|
84
|
+
emit('M4 multi-missing-secrets-negative', {
|
|
85
|
+
threw: slotErr !== null,
|
|
86
|
+
name: slotErr?.name,
|
|
87
|
+
code: slotErr?.code,
|
|
88
|
+
slot: slotErr?.slot,
|
|
89
|
+
success: slotErr !== null && slotErr?.slot === 'secrets',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// matrix 4b (NEGATIVE) — multi without identity -> fail-closed (no silent single)
|
|
93
|
+
reset();
|
|
94
|
+
let identityErr: any = null;
|
|
95
|
+
try {
|
|
96
|
+
createEmbeddedPaymentService({ config: {}, db: freshDb(), tenancy: { mode: 'multi' }, secrets } as any);
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
identityErr = err;
|
|
99
|
+
}
|
|
100
|
+
emit('M4b multi-missing-identity-negative', {
|
|
101
|
+
threw: identityErr !== null,
|
|
102
|
+
name: identityErr?.name,
|
|
103
|
+
code: identityErr?.code,
|
|
104
|
+
slot: identityErr?.slot,
|
|
105
|
+
success: identityErr !== null && identityErr?.slot === 'identity',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// matrix 5 — legacy single compat (no tenancy + APP_PID)
|
|
109
|
+
reset();
|
|
110
|
+
process.env.BLOCKLET_APP_PID = 'did:abt:zLEGACYAPP';
|
|
111
|
+
createEmbeddedPaymentService({ config: { BLOCKLET_APP_PID: 'did:abt:zLEGACYAPP' }, db: freshDb() });
|
|
112
|
+
emit('M5 legacy-single-compat', { tenantMode: getTenantMode(), success: getTenantMode() === 'single' });
|
|
113
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
114
|
+
|
|
115
|
+
console.log('=== DONE ===');
|
|
116
|
+
process.exit(0);
|