@omega.js/backend 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/LICENSE +98 -0
- package/README.md +1053 -0
- package/bin/omega +2 -0
- package/bin/omega-backend +2 -0
- package/bin/omg +2 -0
- package/cli.js +3 -0
- package/dist/cli/command-table.js +268 -0
- package/dist/cli/commands/auth.js +259 -0
- package/dist/cli/commands/base-command.js +451 -0
- package/dist/cli/commands/build.js +27 -0
- package/dist/cli/commands/clean.js +13 -0
- package/dist/cli/commands/clear.js +11 -0
- package/dist/cli/commands/cwd.js +9 -0
- package/dist/cli/commands/deploy.js +205 -0
- package/dist/cli/commands/emulator-orphans.js +440 -0
- package/dist/cli/commands/emulator.js +1829 -0
- package/dist/cli/commands/firebase-init.js +101 -0
- package/dist/cli/commands/firestore.js +261 -0
- package/dist/cli/commands/indexes.js +51 -0
- package/dist/cli/commands/install.js +151 -0
- package/dist/cli/commands/logs.js +358 -0
- package/dist/cli/commands/mcp.js +41 -0
- package/dist/cli/commands/migrate-markers.js +176 -0
- package/dist/cli/commands/migrate-rules.js +90 -0
- package/dist/cli/commands/migrate.js +59 -0
- package/dist/cli/commands/serve.js +208 -0
- package/dist/cli/commands/setup-tests/base-test.js +174 -0
- package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
- package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
- package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
- package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
- package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
- package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
- package/dist/cli/commands/setup-tests/functions-package.js +44 -0
- package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
- package/dist/cli/commands/setup-tests/gitignore.js +122 -0
- package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
- package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
- package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
- package/dist/cli/commands/setup-tests/helpers.js +72 -0
- package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
- package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
- package/dist/cli/commands/setup-tests/index.js +111 -0
- package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
- package/dist/cli/commands/setup-tests/java-installed.js +27 -0
- package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
- package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
- package/dist/cli/commands/setup-tests/node-version.js +60 -0
- package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
- package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
- package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
- package/dist/cli/commands/setup-tests/omega-config.js +84 -0
- package/dist/cli/commands/setup-tests/project-directories.js +34 -0
- package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
- package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
- package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
- package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
- package/dist/cli/commands/setup-tests/service-account.js +86 -0
- package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
- package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
- package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
- package/dist/cli/commands/stripe.js +14 -0
- package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
- package/dist/cli/commands/test.js +976 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/commands/version.js +10 -0
- package/dist/cli/commands/watch.js +196 -0
- package/dist/cli/flags.js +13 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/run.js +33 -0
- package/dist/cli/utils/attach-log-file.js +4 -0
- package/dist/cli/utils/compile-rules.js +1155 -0
- package/dist/cli/utils/ensure-target.js +314 -0
- package/dist/cli/utils/project-type.js +140 -0
- package/dist/cli/utils/public-files.js +49 -0
- package/dist/cli/utils/safe-install.js +4 -0
- package/dist/cli/utils/spawn-shell.js +94 -0
- package/dist/cli/utils/stage-functions.js +338 -0
- package/dist/cli/utils/stage-local-packages.js +210 -0
- package/dist/cli/utils/target-checks.js +161 -0
- package/dist/cli/utils/target.js +24 -0
- package/dist/cli/utils/ui.js +292 -0
- package/dist/defaults/AGENTS.md +125 -0
- package/dist/defaults/CHANGELOG.md +15 -0
- package/dist/defaults/CLAUDE.md +1 -0
- package/dist/defaults/_.gitignore +65 -0
- package/dist/defaults/docs/README.md +17 -0
- package/dist/defaults/test/README.md +65 -0
- package/dist/defaults/test/_init.js +14 -0
- package/dist/defaults/test/helpers/connect-trap.js +72 -0
- package/dist/defaults/test/unit/registration.test.js +143 -0
- package/dist/defaults/test/unit/rules-posture.test.js +101 -0
- package/dist/defaults/test/unit/socket-free.test.js +43 -0
- package/dist/manager/events/auth/before-create.js +89 -0
- package/dist/manager/events/auth/before-signin.js +76 -0
- package/dist/manager/events/auth/on-create.js +112 -0
- package/dist/manager/events/auth/on-delete.js +141 -0
- package/dist/manager/events/auth/utils.js +93 -0
- package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
- package/dist/manager/events/cron/daily/data-requests.js +57 -0
- package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
- package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
- package/dist/manager/events/cron/daily/reset-usage.js +188 -0
- package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
- package/dist/manager/events/cron/daily.js +3 -0
- package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
- package/dist/manager/events/cron/frequent/email-queue.js +71 -0
- package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
- package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
- package/dist/manager/events/cron/frequent.js +3 -0
- package/dist/manager/events/cron/runner.js +64 -0
- package/dist/manager/events/firestore/notifications/on-write.js +123 -0
- package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
- package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
- package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
- package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
- package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
- package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
- package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
- package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
- package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
- package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
- package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
- package/dist/manager/functions/_legacy/admin/query.js +198 -0
- package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
- package/dist/manager/functions/_legacy/template.js +33 -0
- package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
- package/dist/manager/functions/_legacy/test/webhook.js +37 -0
- package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
- package/dist/manager/helpers/analytics.js +498 -0
- package/dist/manager/helpers/api-manager.js +313 -0
- package/dist/manager/helpers/backend-router.js +37 -0
- package/dist/manager/helpers/context/authenticate.js +215 -0
- package/dist/manager/helpers/context/client-info.js +91 -0
- package/dist/manager/helpers/context/index.js +228 -0
- package/dist/manager/helpers/context/logging.js +150 -0
- package/dist/manager/helpers/context/parse.js +160 -0
- package/dist/manager/helpers/context/respond.js +247 -0
- package/dist/manager/helpers/event-middleware.js +109 -0
- package/dist/manager/helpers/metadata.js +32 -0
- package/dist/manager/helpers/middleware.js +541 -0
- package/dist/manager/helpers/redact-secret.js +25 -0
- package/dist/manager/helpers/resolved-config.js +34 -0
- package/dist/manager/helpers/roles.js +69 -0
- package/dist/manager/helpers/safe-compare.js +31 -0
- package/dist/manager/helpers/schema-engine.js +314 -0
- package/dist/manager/helpers/schema-zod.js +203 -0
- package/dist/manager/helpers/settings.js +215 -0
- package/dist/manager/helpers/usage.js +541 -0
- package/dist/manager/helpers/user.js +57 -0
- package/dist/manager/helpers/utilities.js +565 -0
- package/dist/manager/index.js +1404 -0
- package/dist/manager/libraries/abandoned-cart-config.js +12 -0
- package/dist/manager/libraries/ai/index.js +321 -0
- package/dist/manager/libraries/ai/prompt.js +85 -0
- package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
- package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
- package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
- package/dist/manager/libraries/ai/providers/openai.js +1272 -0
- package/dist/manager/libraries/ai/providers/test.js +257 -0
- package/dist/manager/libraries/ai/tokens.js +54 -0
- package/dist/manager/libraries/analytics/conversions.js +478 -0
- package/dist/manager/libraries/analytics/match-data.js +573 -0
- package/dist/manager/libraries/analytics/signup.js +120 -0
- package/dist/manager/libraries/auth-user.js +46 -0
- package/dist/manager/libraries/content/feed-parser.js +198 -0
- package/dist/manager/libraries/content/ghostii.js +191 -0
- package/dist/manager/libraries/content/source-resolver.js +629 -0
- package/dist/manager/libraries/email/constants.js +532 -0
- package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
- package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
- package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
- package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
- package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
- package/dist/manager/libraries/email/data/typo-domains.js +83 -0
- package/dist/manager/libraries/email/disposable-domains.js +92 -0
- package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
- package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
- package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
- package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
- package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
- package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
- package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
- package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
- package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
- package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
- package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
- package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
- package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
- package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
- package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
- package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
- package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
- package/dist/manager/libraries/email/generators/newsletter.js +899 -0
- package/dist/manager/libraries/email/index.js +146 -0
- package/dist/manager/libraries/email/marketing/index.js +754 -0
- package/dist/manager/libraries/email/prepare.js +365 -0
- package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
- package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
- package/dist/manager/libraries/email/transactional/index.js +510 -0
- package/dist/manager/libraries/email/utm.js +86 -0
- package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
- package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
- package/dist/manager/libraries/email/validation.js +255 -0
- package/dist/manager/libraries/env.js +198 -0
- package/dist/manager/libraries/infer-contact.js +101 -0
- package/dist/manager/libraries/load-provider.js +65 -0
- package/dist/manager/libraries/notification.js +256 -0
- package/dist/manager/libraries/openai.js +13 -0
- package/dist/manager/libraries/payment/discount-codes.js +204 -0
- package/dist/manager/libraries/payment/fetch-failure.js +56 -0
- package/dist/manager/libraries/payment/license.js +44 -0
- package/dist/manager/libraries/payment/order-id.js +57 -0
- package/dist/manager/libraries/payment/provider-errors.js +71 -0
- package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
- package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
- package/dist/manager/libraries/payment/providers/paypal.js +987 -0
- package/dist/manager/libraries/payment/providers/stripe.js +755 -0
- package/dist/manager/libraries/payment/providers/test.js +250 -0
- package/dist/manager/libraries/payment/refund-linkage.js +78 -0
- package/dist/manager/libraries/payment/refund-policy.js +113 -0
- package/dist/manager/libraries/payment/winback.js +55 -0
- package/dist/manager/libraries/prompts/infer-contact.md +78 -0
- package/dist/manager/libraries/rate-limits.js +17 -0
- package/dist/manager/libraries/recaptcha.js +58 -0
- package/dist/manager/libraries/user-doc.js +234 -0
- package/dist/manager/routes/admin/backup/post.js +106 -0
- package/dist/manager/routes/admin/cron/post.js +37 -0
- package/dist/manager/routes/admin/database/get.js +35 -0
- package/dist/manager/routes/admin/database/post.js +34 -0
- package/dist/manager/routes/admin/email/post.js +74 -0
- package/dist/manager/routes/admin/firestore/get.js +35 -0
- package/dist/manager/routes/admin/firestore/post.js +56 -0
- package/dist/manager/routes/admin/firestore/query/post.js +128 -0
- package/dist/manager/routes/admin/hook/post.js +104 -0
- package/dist/manager/routes/admin/infer-contact/post.js +35 -0
- package/dist/manager/routes/admin/notification/post.js +37 -0
- package/dist/manager/routes/admin/payment/post.js +56 -0
- package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
- package/dist/manager/routes/admin/post/post.js +487 -0
- package/dist/manager/routes/admin/post/put.js +146 -0
- package/dist/manager/routes/admin/post/templates/post.html +14 -0
- package/dist/manager/routes/admin/repo/content/post.js +106 -0
- package/dist/manager/routes/admin/stats/get.js +222 -0
- package/dist/manager/routes/admin/users/disable/post.js +50 -0
- package/dist/manager/routes/admin/users/list/get.js +110 -0
- package/dist/manager/routes/admin/users/sync/post.js +119 -0
- package/dist/manager/routes/brand/get.js +26 -0
- package/dist/manager/routes/content/post/get.js +110 -0
- package/dist/manager/routes/general/email/post.js +89 -0
- package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
- package/dist/manager/routes/general/uuid/post.js +31 -0
- package/dist/manager/routes/handler/post/post.js +142 -0
- package/dist/manager/routes/health/get.js +43 -0
- package/dist/manager/routes/index.js +11 -0
- package/dist/manager/routes/marketing/campaign/delete.js +45 -0
- package/dist/manager/routes/marketing/campaign/get.js +69 -0
- package/dist/manager/routes/marketing/campaign/post.js +124 -0
- package/dist/manager/routes/marketing/campaign/put.js +86 -0
- package/dist/manager/routes/marketing/campaign/utils.js +59 -0
- package/dist/manager/routes/marketing/contact/delete.js +100 -0
- package/dist/manager/routes/marketing/contact/post.js +149 -0
- package/dist/manager/routes/marketing/contact/put.js +37 -0
- package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
- package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
- package/dist/manager/routes/marketing/webhook/post.js +123 -0
- package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
- package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
- package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
- package/dist/manager/routes/payments/cancel/post.js +149 -0
- package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
- package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
- package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
- package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
- package/dist/manager/routes/payments/discount/get.js +25 -0
- package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
- package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
- package/dist/manager/routes/payments/intent/post.js +341 -0
- package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
- package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
- package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
- package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
- package/dist/manager/routes/payments/intent/providers/test.js +341 -0
- package/dist/manager/routes/payments/plan/post.js +147 -0
- package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
- package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
- package/dist/manager/routes/payments/plan/providers/test.js +113 -0
- package/dist/manager/routes/payments/portal/post.js +113 -0
- package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
- package/dist/manager/routes/payments/portal/providers/test.js +18 -0
- package/dist/manager/routes/payments/refund/post.js +207 -0
- package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
- package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
- package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
- package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
- package/dist/manager/routes/payments/refund/providers/test.js +204 -0
- package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
- package/dist/manager/routes/payments/uncancel/post.js +106 -0
- package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
- package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
- package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
- package/dist/manager/routes/payments/webhook/post.js +157 -0
- package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
- package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
- package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
- package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
- package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
- package/dist/manager/routes/payments/winback/post.js +276 -0
- package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
- package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
- package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
- package/dist/manager/routes/payments/winback/providers/test.js +42 -0
- package/dist/manager/routes/restart/index.js +37 -0
- package/dist/manager/routes/special/electron-client/post.js +59 -0
- package/dist/manager/routes/test/authenticate/get.js +11 -0
- package/dist/manager/routes/test/health/get.js +8 -0
- package/dist/manager/routes/test/index.js +16 -0
- package/dist/manager/routes/test/lab/post.js +17 -0
- package/dist/manager/routes/test/redirect/get.js +27 -0
- package/dist/manager/routes/test/reset-account/post.js +79 -0
- package/dist/manager/routes/test/roster/get.js +40 -0
- package/dist/manager/routes/test/schema/post.js +20 -0
- package/dist/manager/routes/test/usage/post.js +44 -0
- package/dist/manager/routes/test/webhook/post.js +21 -0
- package/dist/manager/routes/user/api-keys/post.js +51 -0
- package/dist/manager/routes/user/connections/_context.js +194 -0
- package/dist/manager/routes/user/connections/_grant.js +247 -0
- package/dist/manager/routes/user/connections/_lease.js +184 -0
- package/dist/manager/routes/user/connections/_providers.js +191 -0
- package/dist/manager/routes/user/connections/_state.js +117 -0
- package/dist/manager/routes/user/connections/delete.js +55 -0
- package/dist/manager/routes/user/connections/get.js +167 -0
- package/dist/manager/routes/user/connections/post.js +370 -0
- package/dist/manager/routes/user/connections/providers/discord.js +41 -0
- package/dist/manager/routes/user/connections/providers/google.js +55 -0
- package/dist/manager/routes/user/connections/providers/kick.js +68 -0
- package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
- package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
- package/dist/manager/routes/user/data-request/delete.js +84 -0
- package/dist/manager/routes/user/data-request/get.js +229 -0
- package/dist/manager/routes/user/data-request/post.js +136 -0
- package/dist/manager/routes/user/delete.js +149 -0
- package/dist/manager/routes/user/feedback/post.js +81 -0
- package/dist/manager/routes/user/get.js +14 -0
- package/dist/manager/routes/user/orders/get.js +95 -0
- package/dist/manager/routes/user/sessions/delete.js +95 -0
- package/dist/manager/routes/user/sessions/get.js +40 -0
- package/dist/manager/routes/user/settings/validate/post.js +86 -0
- package/dist/manager/routes/user/signup/post.js +662 -0
- package/dist/manager/routes/user/subscription/get.js +77 -0
- package/dist/manager/routes/user/token/post.js +30 -0
- package/dist/manager/routes/verts/delete.js +43 -0
- package/dist/manager/routes/verts/get.js +49 -0
- package/dist/manager/routes/verts/post.js +61 -0
- package/dist/manager/routes/verts/put.js +75 -0
- package/dist/manager/routes/verts/redirect/get.js +36 -0
- package/dist/manager/routes/verts/serve/get.js +67 -0
- package/dist/manager/routes/verts/utils.js +436 -0
- package/dist/manager/schemas/admin/backup/post.js +8 -0
- package/dist/manager/schemas/admin/cron/post.js +8 -0
- package/dist/manager/schemas/admin/database/get.js +5 -0
- package/dist/manager/schemas/admin/database/post.js +6 -0
- package/dist/manager/schemas/admin/email/post.js +31 -0
- package/dist/manager/schemas/admin/firestore/get.js +5 -0
- package/dist/manager/schemas/admin/firestore/post.js +8 -0
- package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
- package/dist/manager/schemas/admin/hook/post.js +8 -0
- package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
- package/dist/manager/schemas/admin/notification/post.js +23 -0
- package/dist/manager/schemas/admin/payment/post.js +8 -0
- package/dist/manager/schemas/admin/post/post.js +23 -0
- package/dist/manager/schemas/admin/post/put.js +13 -0
- package/dist/manager/schemas/admin/repo/content/post.js +10 -0
- package/dist/manager/schemas/admin/stats/get.js +5 -0
- package/dist/manager/schemas/admin/users/disable/post.js +9 -0
- package/dist/manager/schemas/admin/users/list/get.js +10 -0
- package/dist/manager/schemas/admin/users/sync/post.js +7 -0
- package/dist/manager/schemas/brand/get.js +3 -0
- package/dist/manager/schemas/content/post/get.js +8 -0
- package/dist/manager/schemas/general/email/post.js +10 -0
- package/dist/manager/schemas/general/uuid/post.js +9 -0
- package/dist/manager/schemas/handler/post/post.js +13 -0
- package/dist/manager/schemas/health/get.js +3 -0
- package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
- package/dist/manager/schemas/marketing/campaign/get.js +13 -0
- package/dist/manager/schemas/marketing/campaign/post.js +43 -0
- package/dist/manager/schemas/marketing/campaign/put.js +37 -0
- package/dist/manager/schemas/marketing/contact/delete.js +8 -0
- package/dist/manager/schemas/marketing/contact/post.js +14 -0
- package/dist/manager/schemas/marketing/contact/put.js +8 -0
- package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
- package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
- package/dist/manager/schemas/marketing/webhook/post.js +7 -0
- package/dist/manager/schemas/payments/cancel/post.js +15 -0
- package/dist/manager/schemas/payments/discount/get.js +8 -0
- package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
- package/dist/manager/schemas/payments/intent/post.js +28 -0
- package/dist/manager/schemas/payments/plan/post.js +11 -0
- package/dist/manager/schemas/payments/portal/post.js +12 -0
- package/dist/manager/schemas/payments/refund/post.js +14 -0
- package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
- package/dist/manager/schemas/payments/uncancel/post.js +9 -0
- package/dist/manager/schemas/payments/webhook/post.js +6 -0
- package/dist/manager/schemas/payments/winback/post.js +12 -0
- package/dist/manager/schemas/restart/index.js +5 -0
- package/dist/manager/schemas/special/electron-client/post.js +11 -0
- package/dist/manager/schemas/test/authenticate/get.js +3 -0
- package/dist/manager/schemas/test/health/get.js +3 -0
- package/dist/manager/schemas/test/index.js +5 -0
- package/dist/manager/schemas/test/lab/post.js +3 -0
- package/dist/manager/schemas/test/redirect/get.js +7 -0
- package/dist/manager/schemas/test/reset-account/post.js +3 -0
- package/dist/manager/schemas/test/roster/get.js +3 -0
- package/dist/manager/schemas/test/schema/post.js +70 -0
- package/dist/manager/schemas/test/usage/post.js +8 -0
- package/dist/manager/schemas/test/webhook/post.js +7 -0
- package/dist/manager/schemas/user/api-keys/post.js +6 -0
- package/dist/manager/schemas/user/connections/delete.js +8 -0
- package/dist/manager/schemas/user/connections/get.js +21 -0
- package/dist/manager/schemas/user/connections/post.js +12 -0
- package/dist/manager/schemas/user/data-request/delete.js +3 -0
- package/dist/manager/schemas/user/data-request/get.js +5 -0
- package/dist/manager/schemas/user/data-request/post.js +6 -0
- package/dist/manager/schemas/user/delete.js +6 -0
- package/dist/manager/schemas/user/feedback/post.js +8 -0
- package/dist/manager/schemas/user/get.js +3 -0
- package/dist/manager/schemas/user/orders/get.js +6 -0
- package/dist/manager/schemas/user/sessions/delete.js +6 -0
- package/dist/manager/schemas/user/sessions/get.js +6 -0
- package/dist/manager/schemas/user/settings/validate/post.js +8 -0
- package/dist/manager/schemas/user/signup/post.js +27 -0
- package/dist/manager/schemas/user/subscription/get.js +5 -0
- package/dist/manager/schemas/user/token/post.js +5 -0
- package/dist/manager/schemas/verts/delete.js +8 -0
- package/dist/manager/schemas/verts/get.js +9 -0
- package/dist/manager/schemas/verts/post.js +23 -0
- package/dist/manager/schemas/verts/put.js +20 -0
- package/dist/manager/schemas/verts/redirect/get.js +10 -0
- package/dist/manager/schemas/verts/serve/get.js +14 -0
- package/dist/manager/server-manager.js +69 -0
- package/dist/mcp/client.js +98 -0
- package/dist/mcp/handler.js +521 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/tools.js +555 -0
- package/dist/mcp/utils.js +108 -0
- package/dist/omega-bin.js +6 -0
- package/dist/require.js +3 -0
- package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
- package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
- package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
- package/dist/test/fixtures/firebase-project/firebase.json +49 -0
- package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
- package/dist/test/fixtures/firebase-project/package.json +14 -0
- package/dist/test/fixtures/firebase-project/src/index.js +11 -0
- package/dist/test/fixtures/firebase-project/storage.rules +8 -0
- package/dist/test/parse-audit.js +25 -0
- package/dist/test/run-tests.js +90 -0
- package/dist/test/runner.js +982 -0
- package/dist/test/seed.js +242 -0
- package/dist/test/test-accounts.js +2660 -0
- package/dist/test/utils/assertions.js +189 -0
- package/dist/test/utils/email-capture.js +252 -0
- package/dist/test/utils/extended-mode-warning.js +11 -0
- package/dist/test/utils/firestore-rules-client.js +277 -0
- package/dist/test/utils/http-client.js +228 -0
- package/dist/test/utils/test-mode-file.js +192 -0
- package/dist/utils/merge-line-files.js +6 -0
- package/dist/utils/scaffold-defaults.js +71 -0
- package/dist/utils/test-lanes.js +62 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/npm-registry.js +52 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/parse-audit.js +74 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/assert.js +120 -0
- package/dist/vendor/devkit/test/define-cases.js +104 -0
- package/dist/vendor/devkit/test/runner-core.js +554 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/env.js +49 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/dist/vendor/monitoring/node.js +72 -0
- package/docs/admin-post-route.md +57 -0
- package/docs/ai-library.md +171 -0
- package/docs/architecture.md +62 -0
- package/docs/audit.md +70 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/build-system.md +65 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/cli-firestore-auth.md +85 -0
- package/docs/cli-logs.md +67 -0
- package/docs/cli-output.md +146 -0
- package/docs/code-patterns.md +77 -0
- package/docs/common-mistakes.md +12 -0
- package/docs/common-operations.md +60 -0
- package/docs/connections.md +212 -0
- package/docs/consent.md +362 -0
- package/docs/directory-structure.md +150 -0
- package/docs/email-system.md +459 -0
- package/docs/environment-detection.md +93 -0
- package/docs/file-naming.md +10 -0
- package/docs/firestore.md +134 -0
- package/docs/ghostii.md +240 -0
- package/docs/index.md +299 -0
- package/docs/key-files.md +37 -0
- package/docs/logging.md +60 -0
- package/docs/marketing-campaigns.md +407 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +222 -0
- package/docs/migration.md +129 -0
- package/docs/payment-system.md +856 -0
- package/docs/paypal-sandbox-qa.md +105 -0
- package/docs/response-headers.md +9 -0
- package/docs/routes.md +211 -0
- package/docs/sanitization.md +71 -0
- package/docs/schemas.md +163 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/stripe-webhook-forwarding.md +20 -0
- package/docs/test-boot-layer.md +67 -0
- package/docs/test-framework.md +583 -0
- package/docs/usage-rate-limiting.md +121 -0
- package/docs/verts.md +29 -0
- package/package.json +143 -0
- package/templates/config/omega.json5 +322 -0
- package/templates/database.rules.json +82 -0
- package/templates/firebase.json +67 -0
- package/templates/firestore.framework.rules +191 -0
- package/templates/firestore.indexes.json +4 -0
- package/templates/firestore.rules +59 -0
- package/templates/index.js +12 -0
- package/templates/public/404.html +26 -0
- package/templates/public/index.html +24 -0
- package/templates/remoteconfig.template.json +1 -0
- package/templates/storage-lifecycle-config-1-day.json +9 -0
- package/templates/storage-lifecycle-config-30-days.json +9 -0
- package/templates/storage.rules +11 -0
- package/test/_init/accounts-validation.js +58 -0
- package/test/ai/tools-live.test.js +171 -0
- package/test/analytics/conversion-delivery.test.js +1013 -0
- package/test/analytics/match-normalization.test.js +238 -0
- package/test/analytics/signup-conversion.test.js +135 -0
- package/test/boot/cli-dispatch.test.js +142 -0
- package/test/boot/defaults-scaffold.test.js +248 -0
- package/test/boot/deploy-staging.test.js +446 -0
- package/test/boot/disposable-domains.test.js +154 -0
- package/test/boot/emulator-boots.test.js +39 -0
- package/test/boot/emulator-port-preflight.test.js +126 -0
- package/test/boot/emulator-ready-timeout.test.js +46 -0
- package/test/boot/project-id-refresh.test.js +73 -0
- package/test/boot/suite-portability.test.js +278 -0
- package/test/boot/update-command.test.js +63 -0
- package/test/cli/cross-platform.test.js +168 -0
- package/test/cli/custom-project-type.test.js +426 -0
- package/test/cli/deploy-license-env.test.js +100 -0
- package/test/cli/emulator-adoption.test.js +254 -0
- package/test/cli/emulator-orphans.test.js +658 -0
- package/test/cli/emulator-ownership.test.js +177 -0
- package/test/cli/emulator-port-retry.test.js +395 -0
- package/test/cli/emulator-shutdown.test.js +788 -0
- package/test/cli/emulator-stale-reap.test.js +574 -0
- package/test/cli/ensure-target.test.js +325 -0
- package/test/cli/flags.test.js +64 -0
- package/test/cli/https-trust.test.js +38 -0
- package/test/cli/install-update-dispatch.test.js +47 -0
- package/test/cli/lane-environments.test.js +159 -0
- package/test/cli/marketing-campaigns-seeded.test.js +194 -0
- package/test/cli/migrate-bare-requires.test.js +128 -0
- package/test/cli/migrate-markers.test.js +530 -0
- package/test/cli/required-indexes.test.js +64 -0
- package/test/cli/rules-compile.test.js +622 -0
- package/test/cli/rules-migration-deferral.test.js +259 -0
- package/test/cli/rules-version.test.js +104 -0
- package/test/cli/setup-load-files.test.js +66 -0
- package/test/cli/setup-offline-mode.test.js +276 -0
- package/test/cli/setup-retired.test.js +64 -0
- package/test/cli/setup-shared-project-indexes.test.js +262 -0
- package/test/cli/setup-tests-requires.test.js +45 -0
- package/test/cli/stage-env-compose.test.js +316 -0
- package/test/cli/stripe-live-lane.test.js +303 -0
- package/test/cli/target.test.js +182 -0
- package/test/cli/templates.test.js +66 -0
- package/test/cli/test-runner-env.test.js +115 -0
- package/test/cli/test-stack-shutdown.test.js +275 -0
- package/test/cli/test-target-no-match.test.js +128 -0
- package/test/cli/verb-logs.test.js +118 -0
- package/test/cli/version-dispatch.test.js +39 -0
- package/test/content/blog-generate.test.js +164 -0
- package/test/email/campaign-config-fault.test.js +376 -0
- package/test/email/campaign-cron-pipeline.test.js +536 -0
- package/test/email/campaign-send.test.js +47 -0
- package/test/email/consent-lifecycle.test.js +258 -0
- package/test/email/content-html-policy.test.js +270 -0
- package/test/email/feedback-and-plain-send.test.js +54 -0
- package/test/email/fixtures/clean.json +30 -0
- package/test/email/fixtures/editorial.json +30 -0
- package/test/email/fixtures/field-report.json +53 -0
- package/test/email/identity.test.js +321 -0
- package/test/email/marketing/consent-gate.test.js +265 -0
- package/test/email/marketing/custom-fields-catalog.test.js +259 -0
- package/test/email/marketing/prune-per-provider.test.js +762 -0
- package/test/email/marketing/remove-log-privacy.test.js +225 -0
- package/test/email/marketing-lifecycle.test.js +139 -0
- package/test/email/newsletter-generate.test.js +853 -0
- package/test/email/newsletter-svg-tokens.test.js +51 -0
- package/test/email/newsletter-templates.test.js +492 -0
- package/test/email/order-one-time-cta.test.js +97 -0
- package/test/email/render-content.test.js +248 -0
- package/test/email/safe-url.test.js +0 -0
- package/test/email/sanitize-images.test.js +66 -0
- package/test/email/send-log-privacy.test.js +107 -0
- package/test/email/templates.test.js +288 -0
- package/test/email/testing-capture.test.js +260 -0
- package/test/email/transactional-send.test.js +36 -0
- package/test/email/transactional.test.js +562 -0
- package/test/email/unsubscribe-groups.test.js +155 -0
- package/test/email/unsubscribe-key.test.js +91 -0
- package/test/email/validation-cases.test.js +152 -0
- package/test/email/validation.test.js +746 -0
- package/test/events/auth-delete-conversion.test.js +138 -0
- package/test/events/auth-delete-race.test.js +211 -0
- package/test/events/auth-on-create-log.test.js +164 -0
- package/test/events/auth-signup-conversion.test.js +141 -0
- package/test/events/auth-signup-limit.test.js +142 -0
- package/test/events/auth-trigger-log-privacy.test.js +256 -0
- package/test/events/cron-job-doc-shape.test.js +101 -0
- package/test/events/cron-reset-usage.test.js +116 -0
- package/test/events/notification-conversion.test.js +182 -0
- package/test/events/payments/_webhook-harness.js +307 -0
- package/test/events/payments/abandoned-cart-activity.test.js +135 -0
- package/test/events/payments/analytics-payment-events.test.js +945 -0
- package/test/events/payments/dispute-email-status.test.js +55 -0
- package/test/events/payments/journey-payments-abandoned.test.js +126 -0
- package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
- package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-cancel.test.js +184 -0
- package/test/events/payments/journey-payments-decline.test.js +225 -0
- package/test/events/payments/journey-payments-discount.test.js +91 -0
- package/test/events/payments/journey-payments-dispute.test.js +190 -0
- package/test/events/payments/journey-payments-failure.test.js +148 -0
- package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
- package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
- package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
- package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
- package/test/events/payments/journey-payments-one-time.test.js +175 -0
- package/test/events/payments/journey-payments-plan-change.test.js +146 -0
- package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
- package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
- package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
- package/test/events/payments/journey-payments-suspend.test.js +183 -0
- package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
- package/test/events/payments/journey-payments-trial.test.js +173 -0
- package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
- package/test/events/payments/journey-payments-uncancel.test.js +157 -0
- package/test/events/payments/journey-payments-upgrade.test.js +137 -0
- package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
- package/test/events/payments/journey-payments-winback.test.js +195 -0
- package/test/events/payments/paypal-expiry-cron.test.js +166 -0
- package/test/events/payments/purchase-failed-handler.test.js +80 -0
- package/test/events/payments/purchase-refunded-handler.test.js +108 -0
- package/test/events/payments/test-processor-doc-shape.test.js +162 -0
- package/test/events/payments/transition-order-emails.test.js +185 -0
- package/test/events/payments/transition-promo-lines.test.js +192 -0
- package/test/events/payments/transitions-detect.test.js +767 -0
- package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
- package/test/events/payments/trial-lapse-sweep.test.js +359 -0
- package/test/events/payments/webhook-atomic-writes.test.js +69 -0
- package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
- package/test/events/payments/webhook-discount-clear.test.js +189 -0
- package/test/events/payments/webhook-failure-intent.test.js +93 -0
- package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
- package/test/events/payments/webhook-ordering.test.js +326 -0
- package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
- package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
- package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
- package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
- package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
- package/test/events/payments/webhook-refund-without-order.test.js +170 -0
- package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
- package/test/events/payments/webhook-refused-intent.test.js +142 -0
- package/test/events/payments/webhook-retry-sweep.test.js +252 -0
- package/test/events/payments/webhook-transition-claim.test.js +389 -0
- package/test/events/payments/webhook-uid-trust.test.js +203 -0
- package/test/events/payments/webhook-user-without-auth.test.js +161 -0
- package/test/fixtures/chargebee/invoice-one-time.json +27 -0
- package/test/fixtures/chargebee/subscription-active.json +44 -0
- package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
- package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
- package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
- package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
- package/test/fixtures/chargebee/subscription-paused.json +42 -0
- package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
- package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
- package/test/fixtures/coinbase/charge-confirmed.json +50 -0
- package/test/fixtures/coinbase/charge-failed.json +34 -0
- package/test/fixtures/coinbase/charge-pending.json +34 -0
- package/test/fixtures/migrate/ported-route.js.txt +11 -0
- package/test/fixtures/paypal/capture-completed.json +51 -0
- package/test/fixtures/paypal/capture-refunded.json +43 -0
- package/test/fixtures/paypal/order-approved.json +62 -0
- package/test/fixtures/paypal/order-completed.json +110 -0
- package/test/fixtures/paypal/sale-refunded.json +38 -0
- package/test/fixtures/paypal/subscription-active.json +76 -0
- package/test/fixtures/paypal/subscription-cancelled.json +50 -0
- package/test/fixtures/paypal/subscription-suspended.json +65 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
- package/test/fixtures/stripe/subscription-active.json +161 -0
- package/test/fixtures/stripe/subscription-canceled.json +161 -0
- package/test/fixtures/stripe/subscription-trialing.json +161 -0
- package/test/helpers/_shared-config.js +15 -0
- package/test/helpers/ai-request-payload.test.js +619 -0
- package/test/helpers/ai-schema-resolve.test.js +125 -0
- package/test/helpers/ai-test-provider.test.js +227 -0
- package/test/helpers/ai-token-accounting.test.js +150 -0
- package/test/helpers/ai-tools-format.test.js +384 -0
- package/test/helpers/analytics-no-id-notice.test.js +146 -0
- package/test/helpers/analytics-user-data.test.js +194 -0
- package/test/helpers/api-manager.test.js +311 -0
- package/test/helpers/backend-router.test.js +136 -0
- package/test/helpers/content/blog-auto-publisher.test.js +482 -0
- package/test/helpers/content/feed-parser.test.js +529 -0
- package/test/helpers/content/ghostii-blocks.test.js +135 -0
- package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
- package/test/helpers/content/ghostii-write-article.test.js +244 -0
- package/test/helpers/dev-only-routes.test.js +225 -0
- package/test/helpers/env-reader.test.js +362 -0
- package/test/helpers/environment.test.js +263 -0
- package/test/helpers/event-middleware.test.js +407 -0
- package/test/helpers/infer-contact.test.js +157 -0
- package/test/helpers/lane-url.test.js +90 -0
- package/test/helpers/merge-line-files.test.js +272 -0
- package/test/helpers/metadata.test.js +122 -0
- package/test/helpers/middleware-request-log.test.js +134 -0
- package/test/helpers/middleware-user-log.test.js +124 -0
- package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
- package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
- package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
- package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
- package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
- package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
- package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
- package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
- package/test/helpers/payment/discount-codes.test.js +141 -0
- package/test/helpers/payment/extract-resource.test.js +113 -0
- package/test/helpers/payment/fetch-failure.test.js +215 -0
- package/test/helpers/payment/license-gate.test.js +108 -0
- package/test/helpers/payment/order-id.test.js +99 -0
- package/test/helpers/payment/paypal/create-intent.test.js +382 -0
- package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
- package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
- package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
- package/test/helpers/payment/paypal/refund-details.test.js +202 -0
- package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
- package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
- package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
- package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
- package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
- package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
- package/test/helpers/payment/stripe/refund-details.test.js +147 -0
- package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
- package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
- package/test/helpers/persona-domain.test.js +87 -0
- package/test/helpers/recaptcha.test.js +166 -0
- package/test/helpers/resolved-config.test.js +106 -0
- package/test/helpers/response-log-redaction.test.js +262 -0
- package/test/helpers/roles.test.js +155 -0
- package/test/helpers/route-context-debug-gate.test.js +118 -0
- package/test/helpers/route-context-logging.test.js +214 -0
- package/test/helpers/route-context.test.js +129 -0
- package/test/helpers/safe-compare.test.js +114 -0
- package/test/helpers/sanitize.test.js +223 -0
- package/test/helpers/schema-engine.test.js +537 -0
- package/test/helpers/schema-zod.test.js +554 -0
- package/test/helpers/seed-accounts-load.test.js +163 -0
- package/test/helpers/seed-google-personas.test.js +114 -0
- package/test/helpers/seeded-personas.test.js +823 -0
- package/test/helpers/settings.test.js +292 -0
- package/test/helpers/setup-engines-pin.test.js +68 -0
- package/test/helpers/setup-manifest-sync.test.js +116 -0
- package/test/helpers/slugify.test.js +395 -0
- package/test/helpers/storage.test.js +195 -0
- package/test/helpers/test-banner-latch.test.js +139 -0
- package/test/helpers/usage-consume.test.js +460 -0
- package/test/helpers/usage-log-privacy.test.js +121 -0
- package/test/helpers/user-doc-heal.test.js +540 -0
- package/test/helpers/user.test.js +771 -0
- package/test/helpers/webhook-forward.test.js +419 -0
- package/test/helpers/wipe-auth-project.test.js +143 -0
- package/test/mcp/discovery.test.js +52 -0
- package/test/mcp/oauth.test.js +160 -0
- package/test/mcp/protocol.test.js +278 -0
- package/test/mcp/roles.test.js +203 -0
- package/test/mcp/utils.test.js +245 -0
- package/test/notification/identity.test.js +155 -0
- package/test/routes/admin/create-post.test.js +362 -0
- package/test/routes/admin/database.test.js +133 -0
- package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
- package/test/routes/admin/email-content-html.test.js +58 -0
- package/test/routes/admin/email-request-log.test.js +86 -0
- package/test/routes/admin/email.test.js +116 -0
- package/test/routes/admin/firestore-query.test.js +206 -0
- package/test/routes/admin/firestore.test.js +129 -0
- package/test/routes/admin/infer-contact.test.js +220 -0
- package/test/routes/admin/notification.test.js +199 -0
- package/test/routes/admin/post-convert-image.test.js +159 -0
- package/test/routes/admin/post-deploy-flag.test.js +81 -0
- package/test/routes/admin/post-download-error.test.js +90 -0
- package/test/routes/admin/post-resize-image.test.js +185 -0
- package/test/routes/admin/post.test.js +369 -0
- package/test/routes/admin/repo-content.test.js +223 -0
- package/test/routes/admin/stats.test.js +114 -0
- package/test/routes/admin/users-disable.test.js +63 -0
- package/test/routes/admin/users-list.test.js +71 -0
- package/test/routes/content/post.test.js +60 -0
- package/test/routes/general/uuid.test.js +133 -0
- package/test/routes/health.test.js +110 -0
- package/test/routes/marketing/campaign.test.js +184 -0
- package/test/routes/marketing/contact.test.js +416 -0
- package/test/routes/marketing/email-preferences.test.js +293 -0
- package/test/routes/marketing/push-send.test.js +34 -0
- package/test/routes/marketing/webhook-forward.test.js +63 -0
- package/test/routes/marketing/webhook.test.js +641 -0
- package/test/routes/payments/_route-harness.js +145 -0
- package/test/routes/payments/cancel-provider-errors.test.js +227 -0
- package/test/routes/payments/cancel-skip-guards.test.js +126 -0
- package/test/routes/payments/cancel-trialing.test.js +428 -0
- package/test/routes/payments/cancel.test.js +165 -0
- package/test/routes/payments/dedup-race.test.js +186 -0
- package/test/routes/payments/discount.test.js +82 -0
- package/test/routes/payments/dispute-alert.test.js +324 -0
- package/test/routes/payments/intent-discount-amounts.test.js +550 -0
- package/test/routes/payments/intent-discount-coupons.test.js +293 -0
- package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
- package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
- package/test/routes/payments/intent-zero-total.test.js +260 -0
- package/test/routes/payments/intent.test.js +413 -0
- package/test/routes/payments/plan.test.js +367 -0
- package/test/routes/payments/portal-return-url.test.js +107 -0
- package/test/routes/payments/portal.test.js +94 -0
- package/test/routes/payments/refund-one-time.test.js +254 -0
- package/test/routes/payments/refund-paypal-proration.test.js +190 -0
- package/test/routes/payments/refund.test.js +181 -0
- package/test/routes/payments/trial-eligibility.test.js +73 -0
- package/test/routes/payments/uncancel.test.js +270 -0
- package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
- package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
- package/test/routes/payments/webhook-test-provider.test.js +156 -0
- package/test/routes/payments/webhook.test.js +115 -0
- package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
- package/test/routes/payments/winback.test.js +597 -0
- package/test/routes/test/authenticate.test.js +79 -0
- package/test/routes/test/redirect.test.js +66 -0
- package/test/routes/test/reset-account.test.js +126 -0
- package/test/routes/test/roster.test.js +101 -0
- package/test/routes/test/schema.test.js +556 -0
- package/test/routes/test/usage.test.js +374 -0
- package/test/routes/user/api-keys.test.js +158 -0
- package/test/routes/user/connections-grant.test.js +759 -0
- package/test/routes/user/connections-identity.test.js +260 -0
- package/test/routes/user/connections-log-privacy.test.js +206 -0
- package/test/routes/user/connections-refresh-lease.test.js +696 -0
- package/test/routes/user/connections-return.test.js +261 -0
- package/test/routes/user/connections-uid.test.js +516 -0
- package/test/routes/user/delete.test.js +138 -0
- package/test/routes/user/feedback.test.js +106 -0
- package/test/routes/user/orders.test.js +233 -0
- package/test/routes/user/sessions.test.js +231 -0
- package/test/routes/user/settings-validate.test.js +84 -0
- package/test/routes/user/signup-emails.test.js +113 -0
- package/test/routes/user/signup-location.test.js +84 -0
- package/test/routes/user/signup-log-privacy.test.js +112 -0
- package/test/routes/user/signup.test.js +579 -0
- package/test/routes/user/subscription.test.js +101 -0
- package/test/routes/user/token.test.js +111 -0
- package/test/routes/user/user.test.js +158 -0
- package/test/routes/verts/cache.test.js +78 -0
- package/test/routes/verts/click-destination.test.js +76 -0
- package/test/routes/verts/crud.test.js +194 -0
- package/test/routes/verts/redirect.test.js +147 -0
- package/test/routes/verts/selection.test.js +237 -0
- package/test/routes/verts/serve.test.js +185 -0
- package/test/routes/verts/unit-document.test.js +117 -0
- package/test/rules/_environment.js +66 -0
- package/test/rules/brand-merge.test.js +195 -0
- package/test/rules/field-helpers.test.js +309 -0
- package/test/rules/notifications.test.js +577 -0
- package/test/rules/payments-carts.test.js +406 -0
- package/test/rules/sessions.test.js +170 -0
- package/test/rules/user.test.js +517 -0
- package/test/rules/verts.test.js +101 -0
- package/test/security/fetch-log-secrets.test.js +124 -0
- package/test/security/primitives.test.js +105 -0
- package/test/security/repo-pinning.test.js +77 -0
- package/test/stripe-live/subscription-lifecycle.test.js +141 -0
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: server conversion delivery — the match data every platform receives
|
|
3
|
+
* ([#385](https://github.com/Omega-JS-Stack/omega/issues/385), stage D of
|
|
4
|
+
* [#302](https://github.com/Omega-JS-Stack/omega/issues/302)).
|
|
5
|
+
*
|
|
6
|
+
* The point of the stage is that a server conversion can be MATCHED to the human
|
|
7
|
+
* who clicked the ad. So these tests read the actual bodies that would go on the
|
|
8
|
+
* wire — built by the real adapters from `@omega.js/analytics`' catalog, fed by
|
|
9
|
+
* the real match-data builders — from a realistic order: attribution with both
|
|
10
|
+
* touches, the platform cookies the checkout collected, the request pair the
|
|
11
|
+
* intent captured, and the tracking-consent snapshot.
|
|
12
|
+
*
|
|
13
|
+
* Four contracts under test:
|
|
14
|
+
* 1. Match data — hashed email/phone (raw PII never leaves), external_id, the
|
|
15
|
+
* click ids and cookies in each platform's own slot, IP + user agent.
|
|
16
|
+
* 2. The consent gate — an explicit `false` blocks its category's providers;
|
|
17
|
+
* an ABSENT snapshot (legacy orders, the raw-API recovery lane) allows all.
|
|
18
|
+
* 3. Absence never throws — an order with no attribution and no request still
|
|
19
|
+
* delivers, on external_id alone.
|
|
20
|
+
* 4. The environment gate — only PRODUCTION reaches a platform; a dev or
|
|
21
|
+
* emulator run resolves the whole walk and delivers nothing
|
|
22
|
+
* ([#464](https://github.com/Omega-JS-Stack/omega/issues/464)).
|
|
23
|
+
*
|
|
24
|
+
* Zero I/O: nothing here reaches a platform. Most assertions are on the bodies
|
|
25
|
+
* the builders return; the two that read the WIRE drive a copy of the delivery
|
|
26
|
+
* module whose transport is a recorder (`fireOnRecordedWire`), so a send is
|
|
27
|
+
* observed without a byte leaving the process.
|
|
28
|
+
*
|
|
29
|
+
* Run: npx omega test framework:analytics/conversion-delivery
|
|
30
|
+
*/
|
|
31
|
+
const crypto = require('crypto');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
const conversions = require('../../dist/manager/libraries/analytics/conversions.js');
|
|
34
|
+
const matchData = require('../../dist/manager/libraries/analytics/match-data.js');
|
|
35
|
+
const defineCases = require('../../dist/vendor/devkit/test/define-cases.js');
|
|
36
|
+
|
|
37
|
+
// A uid the way one actually looks — Firebase's alphabet is case-SENSITIVE —
|
|
38
|
+
// carrying the padding a stored id can pick up. A lowercased or untrimmed
|
|
39
|
+
// digest is a different digest, so a fixture that was already trim and
|
|
40
|
+
// lowercase would have let either drift through unnoticed.
|
|
41
|
+
const UID = ' AYVZezRcE9CsyI ';
|
|
42
|
+
const UID_TRIMMED = 'AYVZezRcE9CsyI';
|
|
43
|
+
// TikTok's Events API documents external_id as "SHA-256 hashing is required",
|
|
44
|
+
// while Meta documents it as "Hashing recommended" and its own Pixel example
|
|
45
|
+
// sends a raw id ([#410](https://github.com/Omega-JS-Stack/omega/issues/410)).
|
|
46
|
+
// So the two platforms take the SAME uid in two shapes, and the digest is
|
|
47
|
+
// hard-coded (shasum of the TRIMMED, case-preserved uid) rather than computed
|
|
48
|
+
// here: a hash that stops matching the browser half's `ttq.identify()` fails
|
|
49
|
+
// here instead of on a live pixel.
|
|
50
|
+
const UID_DIGEST = '12266491d7fe2f07c833999f4d011444564df71cfd7aaafb346f0edf473572b4';
|
|
51
|
+
const EMAIL = 'Buyer@Example.COM';
|
|
52
|
+
const IP = '203.0.113.7';
|
|
53
|
+
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)';
|
|
54
|
+
|
|
55
|
+
// An account doc with everything the schema can hold — what the ONE shared
|
|
56
|
+
// reader (`readUserMatchFields`) turns into the full match block (#577).
|
|
57
|
+
const FULL_USER_DOC = {
|
|
58
|
+
auth: { uid: UID_TRIMMED, email: 'Ada.Lovelace@Example.COM' },
|
|
59
|
+
personal: {
|
|
60
|
+
name: { first: 'Ada', last: 'Lovelace' },
|
|
61
|
+
birthday: { timestamp: '1985-02-16T00:00:00.000Z', timestampUNIX: 477273600 },
|
|
62
|
+
gender: 'female',
|
|
63
|
+
location: { country: 'US', region: 'California', city: 'San Diego', postalCode: '94035' },
|
|
64
|
+
telephone: { countryCode: 1, national: 5550102030 },
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function sha256(value) {
|
|
69
|
+
return crypto.createHash('sha256').update(value).digest('hex');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The attribution shape the landing capture writes and the intent fold carries:
|
|
73
|
+
// an organic first touch, a paid last touch, plus the cookies read at checkout.
|
|
74
|
+
function fullAttribution() {
|
|
75
|
+
return {
|
|
76
|
+
first: {
|
|
77
|
+
tags: { utm_source: 'newsletter', utm_medium: 'email' },
|
|
78
|
+
referrer: 'https://news.ycombinator.com/',
|
|
79
|
+
url: 'https://brand.test/',
|
|
80
|
+
page: '/',
|
|
81
|
+
timestamp: '2026-06-01T00:00:00.000Z',
|
|
82
|
+
},
|
|
83
|
+
last: {
|
|
84
|
+
tags: { utm_source: 'meta', utm_medium: 'cpc', utm_campaign: 'launch', utm_term: 'omega', utm_content: 'hero' },
|
|
85
|
+
clickIds: { fbclid: 'FB-CLICK', gclid: 'G-CLICK', ttclid: 'TT-CLICK' },
|
|
86
|
+
referrer: 'https://facebook.com/',
|
|
87
|
+
url: 'https://brand.test/pricing?utm_source=meta',
|
|
88
|
+
page: '/pricing',
|
|
89
|
+
timestamp: '2026-08-01T00:00:00.000Z',
|
|
90
|
+
},
|
|
91
|
+
affiliate: { code: 'IAN7', timestamp: '2026-08-01T00:00:00.000Z', url: 'https://brand.test/', page: '/' },
|
|
92
|
+
cookies: { fbc: 'fb.1.1754006400000.REAL-COOKIE', fbp: 'fb.1.1754006400000.987654321', ttp: 'TTP-COOKIE' },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const PURCHASE_PARAMS = {
|
|
97
|
+
transaction_id: '_test-sub-conversion',
|
|
98
|
+
value: 9.99,
|
|
99
|
+
currency: 'USD',
|
|
100
|
+
items: [{ item_id: 'premium', item_name: 'Premium', price: 9.99, quantity: 1 }],
|
|
101
|
+
payment_provider: 'stripe',
|
|
102
|
+
payment_frequency: 'monthly',
|
|
103
|
+
is_trial: false,
|
|
104
|
+
is_recurring: false,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Deliver one event exactly as the payment webhook would, and hand back the per-provider results. */
|
|
108
|
+
function deliver({ ctx, Manager, event = 'purchase', params = PURCHASE_PARAMS, attribution = fullAttribution(), trackingConsent = null, providers, identity }) {
|
|
109
|
+
return conversions.deliverConversion({
|
|
110
|
+
event: event,
|
|
111
|
+
params: params,
|
|
112
|
+
providers: providers,
|
|
113
|
+
attribution: matchData.buildAttributionContext(attribution),
|
|
114
|
+
identity: identity || matchData.buildIdentity({
|
|
115
|
+
uid: UID,
|
|
116
|
+
email: EMAIL,
|
|
117
|
+
telephone: { countryCode: 1, national: 5550102030 },
|
|
118
|
+
request: { ip: IP, userAgent: USER_AGENT },
|
|
119
|
+
}),
|
|
120
|
+
trackingConsent: trackingConsent,
|
|
121
|
+
eventId: `${event}._test-webhook-event`,
|
|
122
|
+
ctx: ctx,
|
|
123
|
+
Manager: Manager,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function descriptorFor(results, provider) {
|
|
128
|
+
return results.find((result) => result.provider === provider)?.descriptor;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function outcomeFor(results, provider) {
|
|
132
|
+
return results.find((result) => result.provider === provider)?.outcome;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The pixel ids a brand configures, in the shape the config resolves them from
|
|
136
|
+
// (`analytics.providers.<provider>.id`).
|
|
137
|
+
const META_PIXEL = '_TEST_META_PIXEL';
|
|
138
|
+
const TIKTOK_PIXEL = '_TEST_TIKTOK_PIXEL';
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fire one purchase at the two ad transports with the wire RECORDED.
|
|
142
|
+
*
|
|
143
|
+
* The delivery module is re-required with its `wonderful-fetch` replaced by a
|
|
144
|
+
* recorder, so the real `sendMeta`/`sendTikTok` run — config read, body built,
|
|
145
|
+
* POST issued — and the test reads exactly what a platform would have received.
|
|
146
|
+
* The module and the cache entry are both restored, so every other test in this
|
|
147
|
+
* file keeps the real transport.
|
|
148
|
+
*
|
|
149
|
+
* GA4 is out of the fire: its transport is the Manager's own Measurement
|
|
150
|
+
* Protocol helper rather than this module's `post`, and the server half of a
|
|
151
|
+
* purchase-shaped fire names the two providers that dedupe anyway.
|
|
152
|
+
*
|
|
153
|
+
* @param {object} options
|
|
154
|
+
* @param {boolean} options.production - What the ctx answers for `isProduction()`.
|
|
155
|
+
* @returns {{results: object[], calls: object[], lines: string[]}}
|
|
156
|
+
*/
|
|
157
|
+
function fireOnRecordedWire({ production }) {
|
|
158
|
+
const modulePath = require.resolve('../../dist/manager/libraries/analytics/conversions.js');
|
|
159
|
+
const fetchPath = require.resolve('wonderful-fetch', { paths: [path.dirname(modulePath)] });
|
|
160
|
+
const realFetch = require.cache[fetchPath];
|
|
161
|
+
const savedEnv = { META_ACCESS_TOKEN: process.env.META_ACCESS_TOKEN, TIKTOK_ACCESS_TOKEN: process.env.TIKTOK_ACCESS_TOKEN };
|
|
162
|
+
const calls = [];
|
|
163
|
+
const lines = [];
|
|
164
|
+
const record = (...args) => lines.push(args.map((arg) => String(arg)).join(' '));
|
|
165
|
+
|
|
166
|
+
require.cache[fetchPath] = {
|
|
167
|
+
id: fetchPath,
|
|
168
|
+
filename: fetchPath,
|
|
169
|
+
loaded: true,
|
|
170
|
+
exports: (url, options) => {
|
|
171
|
+
calls.push({ url: url, options: options });
|
|
172
|
+
return Promise.resolve({});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
delete require.cache[modulePath];
|
|
176
|
+
|
|
177
|
+
// A configured brand is the only one that can send at all — without a token
|
|
178
|
+
// both transports answer `skipped (not configured)` and prove nothing.
|
|
179
|
+
process.env.META_ACCESS_TOKEN = '_test-meta-token';
|
|
180
|
+
process.env.TIKTOK_ACCESS_TOKEN = '_test-tiktok-token';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const results = require(modulePath).deliverConversion({
|
|
184
|
+
event: 'purchase',
|
|
185
|
+
params: PURCHASE_PARAMS,
|
|
186
|
+
attribution: matchData.buildAttributionContext(fullAttribution()),
|
|
187
|
+
identity: matchData.buildIdentity({ uid: UID, email: EMAIL, request: { ip: IP, userAgent: USER_AGENT } }),
|
|
188
|
+
providers: ['meta', 'tiktok'],
|
|
189
|
+
eventId: 'purchase._test-wire-event',
|
|
190
|
+
ctx: {
|
|
191
|
+
log: record,
|
|
192
|
+
error: record,
|
|
193
|
+
warn: record,
|
|
194
|
+
isProduction: () => production,
|
|
195
|
+
isDevelopment: () => !production,
|
|
196
|
+
},
|
|
197
|
+
Manager: { config: { analytics: { providers: { meta: { id: META_PIXEL }, tiktok: { id: TIKTOK_PIXEL } } } } },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return { results: results, calls: calls, lines: lines };
|
|
201
|
+
} finally {
|
|
202
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
203
|
+
if (typeof value === 'undefined') {
|
|
204
|
+
delete process.env[key];
|
|
205
|
+
} else {
|
|
206
|
+
process.env[key] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (realFetch) {
|
|
211
|
+
require.cache[fetchPath] = realFetch;
|
|
212
|
+
} else {
|
|
213
|
+
delete require.cache[fetchPath];
|
|
214
|
+
}
|
|
215
|
+
delete require.cache[modulePath];
|
|
216
|
+
require(modulePath);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = defineCases({
|
|
221
|
+
description: 'Server conversion delivery: match data, consent gating, absent-data tolerance',
|
|
222
|
+
type: 'group',
|
|
223
|
+
|
|
224
|
+
tests: [
|
|
225
|
+
{
|
|
226
|
+
name: 'meta-user-data-carries-the-full-match-block',
|
|
227
|
+
auth: 'none',
|
|
228
|
+
|
|
229
|
+
async run({ assert, ctx, Manager }) {
|
|
230
|
+
const results = deliver({ ctx, Manager });
|
|
231
|
+
const body = conversions.buildMetaBody({
|
|
232
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
233
|
+
identity: matchData.buildIdentity({
|
|
234
|
+
uid: UID,
|
|
235
|
+
email: EMAIL,
|
|
236
|
+
telephone: { countryCode: 1, national: 5550102030 },
|
|
237
|
+
request: { ip: IP, userAgent: USER_AGENT },
|
|
238
|
+
}),
|
|
239
|
+
eventId: 'purchase._test-webhook-event',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const event = body.data[0];
|
|
243
|
+
|
|
244
|
+
assert.equal(event.event_name, 'Purchase', 'the catalog names the Meta event');
|
|
245
|
+
assert.equal(event.event_id, 'purchase._test-webhook-event', 'the dedupe id rides the event');
|
|
246
|
+
assert.equal(event.action_source, 'website');
|
|
247
|
+
|
|
248
|
+
// Identity — hashed, never raw
|
|
249
|
+
assert.equal(event.user_data.em, sha256('buyer@example.com'), 'email is SHA256 of the trimmed lowercase address');
|
|
250
|
+
assert.equal(event.user_data.ph, sha256('15550102030'), 'phone is SHA256 of the digits, country code included');
|
|
251
|
+
assert.equal(event.user_data.external_id, UID, 'Meta only RECOMMENDS hashing external_id, and its Pixel half sends the raw uid (#410)');
|
|
252
|
+
assert.equal(JSON.stringify(event).includes(EMAIL), false, 'the raw email must never appear anywhere in the body');
|
|
253
|
+
assert.equal(JSON.stringify(event).includes('5550102030'), false, 'the raw phone number must never appear anywhere in the body');
|
|
254
|
+
|
|
255
|
+
// Same-device match
|
|
256
|
+
assert.equal(event.user_data.client_ip_address, IP);
|
|
257
|
+
assert.equal(event.user_data.client_user_agent, USER_AGENT);
|
|
258
|
+
assert.equal(event.user_data.fbc, 'fb.1.1754006400000.REAL-COOKIE', 'the real cookie wins over any construction');
|
|
259
|
+
assert.equal(event.user_data.fbp, 'fb.1.1754006400000.987654321');
|
|
260
|
+
|
|
261
|
+
// Commerce, in Meta's own vocabulary (the catalog's map)
|
|
262
|
+
assert.equal(event.custom_data.value, 9.99);
|
|
263
|
+
assert.equal(event.custom_data.currency, 'USD');
|
|
264
|
+
assert.deepEqual(event.custom_data.content_ids, ['premium']);
|
|
265
|
+
assert.equal(event.custom_data.fbc, undefined, 'match data never rides the custom data');
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
{
|
|
270
|
+
// Meta REQUIRES `action_source` and makes its accuracy a term of use, and
|
|
271
|
+
// its enum names this exact case: `system_generated`, "for example, a
|
|
272
|
+
// subscription renewal that's set to auto-pay each month". Every server
|
|
273
|
+
// fire claimed `website`, which said a person had been on the site for a
|
|
274
|
+
// charge their card took on its own
|
|
275
|
+
// ([#498](https://github.com/Omega-JS-Stack/omega/issues/498)). The
|
|
276
|
+
// catalog decides it per event; the body is where it has to show up.
|
|
277
|
+
name: 'a-billed-charge-tells-meta-system-generated-and-a-checkout-website',
|
|
278
|
+
auth: 'none',
|
|
279
|
+
|
|
280
|
+
async run({ assert, ctx, Manager }) {
|
|
281
|
+
const identity = matchData.buildIdentity({ uid: UID, email: EMAIL, request: { ip: IP, userAgent: USER_AGENT } });
|
|
282
|
+
const actionSourceFor = (event) => conversions.buildMetaBody({
|
|
283
|
+
descriptor: descriptorFor(deliver({ ctx, Manager, event: event }), 'meta'),
|
|
284
|
+
identity: identity,
|
|
285
|
+
eventId: `${event}._test-webhook-event`,
|
|
286
|
+
}).data[0].action_source;
|
|
287
|
+
|
|
288
|
+
for (const event of ['subscription_renew', 'payment_recovered', 'trial_convert']) {
|
|
289
|
+
assert.equal(actionSourceFor(event), 'system_generated', `${event} bills a card with nobody present`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
for (const event of ['purchase', 'trial_start', 'subscription_cancel']) {
|
|
293
|
+
assert.equal(actionSourceFor(event), 'website', `${event} is something a person did on the site`);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
{
|
|
299
|
+
// The match block a real account can fill
|
|
300
|
+
// ([#577](https://github.com/Omega-JS-Stack/omega/issues/577)): the doc
|
|
301
|
+
// already held the name, birthday, gender and location, and Meta accepts
|
|
302
|
+
// all four — so the body carries every parameter, not just `em` +
|
|
303
|
+
// `external_id`. The normalization each one needs is pinned value by
|
|
304
|
+
// value in analytics/match-normalization.test.js; what this asserts is
|
|
305
|
+
// that the BODY carries them, from the one shared reader.
|
|
306
|
+
name: 'a-full-account-fills-every-meta-parameter',
|
|
307
|
+
auth: 'none',
|
|
308
|
+
|
|
309
|
+
async run({ assert, ctx, Manager }) {
|
|
310
|
+
const identity = matchData.buildIdentity({
|
|
311
|
+
uid: UID,
|
|
312
|
+
user: FULL_USER_DOC,
|
|
313
|
+
request: { ip: IP, userAgent: USER_AGENT },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const results = deliver({ ctx, Manager, identity });
|
|
317
|
+
const userData = conversions.buildMetaBody({
|
|
318
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
319
|
+
identity: identity,
|
|
320
|
+
eventId: 'purchase._test-webhook-event',
|
|
321
|
+
}).data[0].user_data;
|
|
322
|
+
|
|
323
|
+
assert.deepEqual(
|
|
324
|
+
Object.keys(userData).sort(),
|
|
325
|
+
['client_ip_address', 'client_user_agent', 'country', 'ct', 'db', 'em', 'external_id', 'fbc', 'fbp', 'fn', 'ge', 'ln', 'ph', 'st', 'zp'],
|
|
326
|
+
'every parameter Meta accepts from this account rides the body',
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// The email and phone still come off the SAME reader — a call site that
|
|
330
|
+
// passes a doc must not lose the two keys it always had.
|
|
331
|
+
assert.equal(userData.em, sha256('ada.lovelace@example.com'), 'the doc\'s email is read and normalized like any other');
|
|
332
|
+
assert.equal(userData.ph, sha256('15550102030'), 'and its telephone pair');
|
|
333
|
+
assert.equal(JSON.stringify(userData).includes('Lovelace'), false, 'no raw personal value anywhere in the block');
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
{
|
|
338
|
+
// The fire log is how a brand SEES its match quality without opening an
|
|
339
|
+
// ads manager: per provider, the keys that went and the accepted keys
|
|
340
|
+
// that were empty. KEY NAMES ONLY — a match value, raw or hashed, must
|
|
341
|
+
// never reach a log line (#577).
|
|
342
|
+
name: 'the-fire-log-names-the-match-keys-sent-and-the-ones-that-were-empty',
|
|
343
|
+
auth: 'none',
|
|
344
|
+
|
|
345
|
+
async run({ assert, Manager }) {
|
|
346
|
+
const lines = [];
|
|
347
|
+
const record = (...args) => lines.push(args.map((arg) => String(arg)).join(' '));
|
|
348
|
+
const identity = matchData.buildIdentity({ uid: UID, email: EMAIL, request: { ip: IP, userAgent: USER_AGENT } });
|
|
349
|
+
|
|
350
|
+
conversions.deliverConversion({
|
|
351
|
+
event: 'purchase',
|
|
352
|
+
params: PURCHASE_PARAMS,
|
|
353
|
+
attribution: matchData.buildAttributionContext({}),
|
|
354
|
+
identity: identity,
|
|
355
|
+
providers: ['meta'],
|
|
356
|
+
eventId: 'purchase._test-fire-log',
|
|
357
|
+
ctx: { log: record, error: record, warn: record, isProduction: () => false, isDevelopment: () => true },
|
|
358
|
+
Manager: Manager,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const summary = lines.find((line) => line.startsWith('deliverConversion: purchase →'));
|
|
362
|
+
|
|
363
|
+
assert.equal(!!summary, true, `the walk logs one summary line: ${lines.join(' | ')}`);
|
|
364
|
+
assert.equal(summary.includes('meta blocked (dev) sent em,external_id,client_ip_address,client_user_agent;'), true, `the line names what went: ${summary}`);
|
|
365
|
+
assert.equal(summary.includes('empty ph,fn,ln,ct,st,zp,country,db,ge,fbc,fbp'), true, `and what the platform accepts but this fire had nothing for: ${summary}`);
|
|
366
|
+
assert.equal(summary.includes(EMAIL), false, 'never the raw value');
|
|
367
|
+
assert.equal(summary.includes(identity.emailHash), false, 'and never the hashed one either');
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
{
|
|
372
|
+
name: 'meta-constructs-fbc-from-the-captured-fbclid',
|
|
373
|
+
auth: 'none',
|
|
374
|
+
|
|
375
|
+
async run({ assert, ctx, Manager }) {
|
|
376
|
+
// The cookie never made it to checkout (cleared jar, ITP, another device)
|
|
377
|
+
const attribution = fullAttribution();
|
|
378
|
+
delete attribution.cookies.fbc;
|
|
379
|
+
|
|
380
|
+
const results = deliver({ ctx, Manager, attribution });
|
|
381
|
+
const descriptor = descriptorFor(results, 'meta');
|
|
382
|
+
const capturedMs = Date.parse('2026-08-01T00:00:00.000Z');
|
|
383
|
+
|
|
384
|
+
assert.equal(descriptor.userData.fbc, `fb.1.${capturedMs}.FB-CLICK`, 'fbc is built per Meta spec from the click id, stamped when it was captured');
|
|
385
|
+
assert.equal(descriptor.userData.fbp, 'fb.1.1754006400000.987654321', 'the browser cookie is untouched by the construction');
|
|
386
|
+
|
|
387
|
+
// Nothing to build from is not an error — the fire still goes with what it has
|
|
388
|
+
const noClick = fullAttribution();
|
|
389
|
+
delete noClick.cookies.fbc;
|
|
390
|
+
delete noClick.last.clickIds.fbclid;
|
|
391
|
+
|
|
392
|
+
const bare = descriptorFor(deliver({ ctx, Manager, attribution: noClick }), 'meta');
|
|
393
|
+
assert.equal(bare.userData.fbc, undefined, 'no cookie and no fbclid means no fbc, never a malformed one');
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
{
|
|
398
|
+
// Events API 2.0 FLATTENED the match block: what used to sit under
|
|
399
|
+
// `data[].context` (`context.user`, `context.ad.callback`, `context.ip`,
|
|
400
|
+
// `context.user_agent`) is one `data[].user` object, with the click id as
|
|
401
|
+
// a plain `user.ttclid` and the phone under `phone`, not `phone_number`
|
|
402
|
+
// ([#482](https://github.com/Omega-JS-Stack/omega/issues/482)). An
|
|
403
|
+
// off-spec key does not error — it matches nobody, silently.
|
|
404
|
+
name: 'tiktok-user-block-carries-the-full-match-block',
|
|
405
|
+
auth: 'none',
|
|
406
|
+
|
|
407
|
+
async run({ assert, ctx, Manager }) {
|
|
408
|
+
const results = deliver({ ctx, Manager });
|
|
409
|
+
const body = conversions.buildTikTokBody({
|
|
410
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
411
|
+
identity: matchData.buildIdentity({
|
|
412
|
+
uid: UID,
|
|
413
|
+
email: EMAIL,
|
|
414
|
+
telephone: { countryCode: 1, national: 5550102030 },
|
|
415
|
+
request: { ip: IP, userAgent: USER_AGENT },
|
|
416
|
+
}),
|
|
417
|
+
eventId: 'purchase._test-webhook-event',
|
|
418
|
+
pixelCode: '_TEST_PIXEL',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const event = body.data[0];
|
|
422
|
+
|
|
423
|
+
assert.equal(event.event, 'Purchase', 'the catalog names the TikTok event — Purchase, since CompletePayment was retired ([#652])');
|
|
424
|
+
assert.equal(event.event_id, 'purchase._test-webhook-event');
|
|
425
|
+
assert.equal(event.user.email, sha256('buyer@example.com'), 'TikTok takes the email hashed too');
|
|
426
|
+
assert.equal(event.user.phone, sha256('+15550102030'), 'TikTok\'s spec is the E.164 number, plus included');
|
|
427
|
+
assert.equal(event.user.external_id, UID_DIGEST, 'TikTok REQUIRES external_id hashed, and a raw one matches nobody (#410)');
|
|
428
|
+
assert.equal(JSON.stringify(event).includes(UID_TRIMMED), false, 'the raw uid must not ride a TikTok body under any key');
|
|
429
|
+
assert.equal(event.user.ttp, 'TTP-COOKIE');
|
|
430
|
+
assert.equal(event.user.ttclid, 'TT-CLICK', 'the click id is a match key like any other in 2.0');
|
|
431
|
+
assert.equal(event.user.ip, IP);
|
|
432
|
+
assert.equal(event.user.user_agent, USER_AGENT);
|
|
433
|
+
assert.equal(event.properties.contents[0].content_id, 'premium', 'TikTok commerce rides its documented `contents` array ([#652])');
|
|
434
|
+
assert.equal(event.properties.value, 9.99);
|
|
435
|
+
|
|
436
|
+
// The v1.2 nesting is GONE, not duplicated alongside — a body carrying
|
|
437
|
+
// both shapes is how the two drift apart on the next edit.
|
|
438
|
+
assert.equal(Object.hasOwn(event, 'context'), false, 'the v1.2 context block never rides along');
|
|
439
|
+
assert.equal(JSON.stringify(event).includes('phone_number'), false, 'nor its key names');
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
{
|
|
444
|
+
// `timestamp` was an ISO string; 2.0's REQUIRED field is `event_time`, in
|
|
445
|
+
// Unix SECONDS — the same stamp Meta's body has always carried. An ISO
|
|
446
|
+
// string here is either a rejection or an event attributed to the wrong
|
|
447
|
+
// day, and neither shows up anywhere but the platform's own dashboard
|
|
448
|
+
// ([#482](https://github.com/Omega-JS-Stack/omega/issues/482)).
|
|
449
|
+
name: 'the-tiktok-item-stamps-event-time-in-unix-seconds',
|
|
450
|
+
auth: 'none',
|
|
451
|
+
|
|
452
|
+
async run({ assert, ctx, Manager }) {
|
|
453
|
+
const before = Math.floor(Date.now() / 1000);
|
|
454
|
+
const results = deliver({ ctx, Manager });
|
|
455
|
+
const event = conversions.buildTikTokBody({
|
|
456
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
457
|
+
identity: matchData.buildIdentity({ uid: UID }),
|
|
458
|
+
eventId: 'purchase._test-webhook-event',
|
|
459
|
+
pixelCode: TIKTOK_PIXEL,
|
|
460
|
+
}).data[0];
|
|
461
|
+
|
|
462
|
+
assert.equal(Number.isInteger(event.event_time), true, `event_time is an integer of seconds, not ${JSON.stringify(event.event_time)}`);
|
|
463
|
+
assert.equal(event.event_time >= before, true, 'stamped at build time');
|
|
464
|
+
assert.equal(event.event_time <= Math.floor(Date.now() / 1000), true, 'and never in the future');
|
|
465
|
+
// Milliseconds pass Number.isInteger too, and are the failure the unit
|
|
466
|
+
// gets wrong silently: a 13-digit stamp reads as the year 46,000.
|
|
467
|
+
assert.equal(String(event.event_time).length, 10, 'seconds, not milliseconds');
|
|
468
|
+
assert.equal(Object.hasOwn(event, 'timestamp'), false, 'the v1.2 ISO field is replaced, not kept beside it');
|
|
469
|
+
assert.equal(JSON.stringify(event).includes('T00:00:00'), false, 'no ISO stamp rides the item under any key');
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
{
|
|
474
|
+
// TikTok's Events API validates the DESTINATION at the top level of the
|
|
475
|
+
// request: `event_source` and `event_source_id`. The pixel id used to ride
|
|
476
|
+
// as `data[].pixel_code` instead, so every live fire came back
|
|
477
|
+
// `{"code":40002,"message":"Invalid value for event_source_id: not a valid
|
|
478
|
+
// string."}` — the field TikTok validates was never sent, while the id sat
|
|
479
|
+
// correctly in config the whole time
|
|
480
|
+
// ([#465](https://github.com/Omega-JS-Stack/omega/issues/465)).
|
|
481
|
+
name: 'the-tiktok-body-names-the-pixel-in-event-source-id',
|
|
482
|
+
auth: 'none',
|
|
483
|
+
|
|
484
|
+
async run({ assert, ctx, Manager }) {
|
|
485
|
+
const results = deliver({ ctx, Manager });
|
|
486
|
+
const body = conversions.buildTikTokBody({
|
|
487
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
488
|
+
identity: matchData.buildIdentity({ uid: UID }),
|
|
489
|
+
eventId: 'purchase._test-webhook-event',
|
|
490
|
+
pixelCode: TIKTOK_PIXEL,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
assert.equal(body.event_source_id, TIKTOK_PIXEL, 'the configured pixel id is the event source id TikTok validates');
|
|
494
|
+
assert.equal(body.event_source, 'web', 'a conversion for a website is the web source');
|
|
495
|
+
assert.equal(body.data[0].pixel_code, undefined, 'one home for the id — a second copy in the data item is what drifted');
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
{
|
|
500
|
+
name: 'the-tiktok-send-puts-the-configured-pixel-on-the-wire',
|
|
501
|
+
auth: 'none',
|
|
502
|
+
|
|
503
|
+
async run({ assert }) {
|
|
504
|
+
// The body test above proves the SHAPE; this one proves the id actually
|
|
505
|
+
// reaches it — the resolution step (#465) is where the send went wrong.
|
|
506
|
+
const { calls, lines } = fireOnRecordedWire({ production: true });
|
|
507
|
+
const tiktok = calls.find((call) => call.url.includes('business-api.tiktok.com'));
|
|
508
|
+
|
|
509
|
+
assert.equal(!!tiktok, true, `a configured brand in production must reach TikTok: ${lines.join(' | ')}`);
|
|
510
|
+
assert.equal(tiktok.options.body.event_source_id, TIKTOK_PIXEL, 'the id on the wire is the one config resolved');
|
|
511
|
+
assert.equal(tiktok.options.body.event_source, 'web');
|
|
512
|
+
assert.equal(JSON.stringify(tiktok.options.body).includes('pixel_code'), false, 'the v1.2 field never rides along');
|
|
513
|
+
|
|
514
|
+
// The other transport is the control: it was delivering all along.
|
|
515
|
+
const meta = calls.find((call) => call.url.includes('graph.facebook.com'));
|
|
516
|
+
|
|
517
|
+
assert.equal(!!meta, true, `Meta must still be reached: ${lines.join(' | ')}`);
|
|
518
|
+
assert.equal(meta.url.includes(`/${META_PIXEL}/events`), true, 'Meta names its pixel in the path');
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
{
|
|
523
|
+
// A server conversion has no page of its own — a webhook fires from
|
|
524
|
+
// Stripe's servers — so the only URL it can honestly claim is the one the
|
|
525
|
+
// attribution touch captured. It used to be dropped on the floor by
|
|
526
|
+
// `buildAttributionContext()`, so TikTok's data item carried no `page` and
|
|
527
|
+
// Meta's event no `event_source_url`, and both platforms scored the match
|
|
528
|
+
// lower for it ([#497](https://github.com/Omega-JS-Stack/omega/issues/497)).
|
|
529
|
+
name: 'the-page-the-conversion-happened-on-rides-both-ad-bodies',
|
|
530
|
+
auth: 'none',
|
|
531
|
+
|
|
532
|
+
async run({ assert, ctx, Manager }) {
|
|
533
|
+
const context = matchData.buildAttributionContext(fullAttribution());
|
|
534
|
+
|
|
535
|
+
assert.equal(context.url, 'https://brand.test/pricing?utm_source=meta', 'the CREDITED touch\'s url reaches the adapters');
|
|
536
|
+
assert.equal(context.referrer, 'https://facebook.com/', 'and the referrer it landed from');
|
|
537
|
+
|
|
538
|
+
const results = deliver({ ctx, Manager });
|
|
539
|
+
const identity = matchData.buildIdentity({ uid: UID });
|
|
540
|
+
|
|
541
|
+
const meta = conversions.buildMetaBody({
|
|
542
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
543
|
+
identity: identity,
|
|
544
|
+
eventId: 'purchase._test-webhook-event',
|
|
545
|
+
}).data[0];
|
|
546
|
+
|
|
547
|
+
const tiktok = conversions.buildTikTokBody({
|
|
548
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
549
|
+
identity: identity,
|
|
550
|
+
eventId: 'purchase._test-webhook-event',
|
|
551
|
+
pixelCode: TIKTOK_PIXEL,
|
|
552
|
+
}).data[0];
|
|
553
|
+
|
|
554
|
+
assert.equal(meta.event_source_url, 'https://brand.test/pricing?utm_source=meta', 'Meta reads the conversion\'s page as event_source_url');
|
|
555
|
+
assert.deepEqual(tiktok.page, { url: 'https://brand.test/pricing?utm_source=meta', referrer: 'https://facebook.com/' }, 'TikTok 2.0 reads it as the item\'s page object');
|
|
556
|
+
|
|
557
|
+
// It is context, not commerce and not a match key — a url in either
|
|
558
|
+
// block is an off-spec field the platform reads as neither.
|
|
559
|
+
assert.equal(meta.custom_data.url, undefined, 'the page never rides Meta\'s custom data');
|
|
560
|
+
assert.equal(tiktok.properties.url, undefined, 'nor TikTok\'s properties');
|
|
561
|
+
assert.equal(tiktok.user.url, undefined, 'nor either match block');
|
|
562
|
+
assert.equal(meta.user_data.url, undefined);
|
|
563
|
+
|
|
564
|
+
// GA4's Measurement Protocol gets no invented slot: the campaign params
|
|
565
|
+
// are what its half of this attribution has always been.
|
|
566
|
+
assert.equal(descriptorFor(results, 'ga4').payload.url, undefined, 'GA4 gains no url param');
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
{
|
|
571
|
+
name: 'a-direct-landing-sends-a-page-of-the-url-alone',
|
|
572
|
+
auth: 'none',
|
|
573
|
+
|
|
574
|
+
async run({ assert, ctx, Manager }) {
|
|
575
|
+
// The landing capture writes `referrer: null` for a visit that came from
|
|
576
|
+
// no page at all (a typed url, an app link). An empty referrer key is a
|
|
577
|
+
// field we tried and failed to send.
|
|
578
|
+
const attribution = fullAttribution();
|
|
579
|
+
attribution.last.referrer = null;
|
|
580
|
+
|
|
581
|
+
const results = deliver({ ctx, Manager, attribution });
|
|
582
|
+
const identity = matchData.buildIdentity({ uid: UID });
|
|
583
|
+
|
|
584
|
+
const meta = conversions.buildMetaBody({
|
|
585
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
586
|
+
identity: identity,
|
|
587
|
+
eventId: 'purchase._test-webhook-event',
|
|
588
|
+
}).data[0];
|
|
589
|
+
|
|
590
|
+
const tiktok = conversions.buildTikTokBody({
|
|
591
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
592
|
+
identity: identity,
|
|
593
|
+
eventId: 'purchase._test-webhook-event',
|
|
594
|
+
pixelCode: TIKTOK_PIXEL,
|
|
595
|
+
}).data[0];
|
|
596
|
+
|
|
597
|
+
assert.equal(meta.event_source_url, 'https://brand.test/pricing?utm_source=meta', 'the url still goes — a referrer was never part of Meta\'s field');
|
|
598
|
+
assert.deepEqual(tiktok.page, { url: 'https://brand.test/pricing?utm_source=meta' }, 'the page is the url alone');
|
|
599
|
+
assert.equal(Object.hasOwn(tiktok.page, 'referrer'), false, 'never an empty referrer beside it');
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
{
|
|
604
|
+
name: 'no-touch-url-means-no-page-and-no-event-source-url',
|
|
605
|
+
auth: 'none',
|
|
606
|
+
|
|
607
|
+
async run({ assert, ctx, Manager }) {
|
|
608
|
+
// The raw-API recovery lane: no browser was ever involved, so there is
|
|
609
|
+
// no touch and no url. An invented one is worse than an absent one —
|
|
610
|
+
// the brand's home page would credit every recovered order to a page
|
|
611
|
+
// nobody converted on.
|
|
612
|
+
const results = deliver({ ctx, Manager, attribution: {}, identity: matchData.buildIdentity({ uid: UID }) });
|
|
613
|
+
const identity = matchData.buildIdentity({ uid: UID });
|
|
614
|
+
|
|
615
|
+
const meta = conversions.buildMetaBody({
|
|
616
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
617
|
+
identity: identity,
|
|
618
|
+
eventId: 'purchase._test-webhook-event',
|
|
619
|
+
}).data[0];
|
|
620
|
+
|
|
621
|
+
const tiktok = conversions.buildTikTokBody({
|
|
622
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
623
|
+
identity: identity,
|
|
624
|
+
eventId: 'purchase._test-webhook-event',
|
|
625
|
+
pixelCode: TIKTOK_PIXEL,
|
|
626
|
+
}).data[0];
|
|
627
|
+
|
|
628
|
+
assert.equal(Object.hasOwn(meta, 'event_source_url'), false, 'Meta gets no key at all, never an empty string');
|
|
629
|
+
assert.equal(Object.hasOwn(tiktok, 'page'), false, 'and TikTok no empty page object');
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
{
|
|
634
|
+
name: 'ga4-gains-the-campaign-params-and-gclid',
|
|
635
|
+
auth: 'none',
|
|
636
|
+
|
|
637
|
+
async run({ assert, ctx, Manager }) {
|
|
638
|
+
const descriptor = descriptorFor(deliver({ ctx, Manager }), 'ga4');
|
|
639
|
+
|
|
640
|
+
assert.equal(descriptor.name, 'purchase');
|
|
641
|
+
assert.equal(descriptor.payload.source, 'meta', 'last touch is the credited one');
|
|
642
|
+
assert.equal(descriptor.payload.medium, 'cpc');
|
|
643
|
+
assert.equal(descriptor.payload.campaign, 'launch');
|
|
644
|
+
assert.equal(descriptor.payload.term, 'omega');
|
|
645
|
+
assert.equal(descriptor.payload.content, 'hero');
|
|
646
|
+
assert.equal(descriptor.payload.gclid, 'G-CLICK', 'the Google click id rides as an event param');
|
|
647
|
+
assert.equal(descriptor.payload.value, 9.99, 'the commerce params are untouched');
|
|
648
|
+
assert.equal(descriptor.payload.fbc, undefined, 'another platform\'s match data never rides GA4');
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
{
|
|
653
|
+
name: 'campaign-falls-back-to-the-first-touch',
|
|
654
|
+
auth: 'none',
|
|
655
|
+
|
|
656
|
+
async run({ assert, ctx, Manager }) {
|
|
657
|
+
// A user who arrived on a tagged link and never came back through one:
|
|
658
|
+
// last touch is only ever written by a tagged visit (#384).
|
|
659
|
+
const attribution = fullAttribution();
|
|
660
|
+
delete attribution.last;
|
|
661
|
+
|
|
662
|
+
const descriptor = descriptorFor(deliver({ ctx, Manager, attribution }), 'ga4');
|
|
663
|
+
|
|
664
|
+
assert.equal(descriptor.payload.source, 'newsletter', 'first touch credits the conversion when there is no last');
|
|
665
|
+
assert.equal(descriptor.payload.medium, 'email');
|
|
666
|
+
assert.equal(descriptor.payload.campaign, undefined, 'a param the touch never carried is absent, not empty');
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
{
|
|
671
|
+
name: 'marketing-declined-blocks-meta-and-tiktok-but-not-ga4',
|
|
672
|
+
auth: 'none',
|
|
673
|
+
|
|
674
|
+
async run({ assert, ctx, Manager }) {
|
|
675
|
+
const results = deliver({
|
|
676
|
+
ctx,
|
|
677
|
+
Manager,
|
|
678
|
+
trackingConsent: { analytics: true, marketing: false, region: 'opt-in', version: 1 },
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
assert.equal(outcomeFor(results, 'meta'), 'skipped (consent: marketing)', 'an opted-out user reaches no ad platform');
|
|
682
|
+
assert.equal(outcomeFor(results, 'tiktok'), 'skipped (consent: marketing)');
|
|
683
|
+
assert.equal(descriptorFor(results, 'meta'), undefined, 'a blocked provider never even resolves its payload');
|
|
684
|
+
assert.equal(outcomeFor(results, 'ga4').startsWith('skipped (consent'), false, 'analytics was granted, so GA4 still counts it');
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
{
|
|
689
|
+
name: 'analytics-declined-blocks-ga4-but-not-the-ad-platforms',
|
|
690
|
+
auth: 'none',
|
|
691
|
+
|
|
692
|
+
async run({ assert, ctx, Manager }) {
|
|
693
|
+
const results = deliver({
|
|
694
|
+
ctx,
|
|
695
|
+
Manager,
|
|
696
|
+
trackingConsent: { analytics: false, marketing: true, region: 'opt-in', version: 1 },
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
assert.equal(outcomeFor(results, 'ga4'), 'skipped (consent: analytics)');
|
|
700
|
+
assert.equal(outcomeFor(results, 'meta').startsWith('skipped (consent'), false, 'the two categories gate independently');
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
{
|
|
705
|
+
name: 'an-absent-snapshot-is-not-a-denial',
|
|
706
|
+
auth: 'none',
|
|
707
|
+
|
|
708
|
+
async run({ assert, ctx, Manager }) {
|
|
709
|
+
// A legacy order, or one minted by the raw-API recovery lane, carries no
|
|
710
|
+
// snapshot at all. Blocking it would be a silent revenue hole.
|
|
711
|
+
for (const snapshot of [null, undefined, {}]) {
|
|
712
|
+
const granted = conversions.resolveTrackingConsent(snapshot);
|
|
713
|
+
|
|
714
|
+
assert.equal(granted.analytics, true, `${JSON.stringify(snapshot)} allows analytics`);
|
|
715
|
+
assert.equal(granted.marketing, true, `${JSON.stringify(snapshot)} allows marketing`);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const results = deliver({ ctx, Manager, trackingConsent: null });
|
|
719
|
+
|
|
720
|
+
for (const provider of ['ga4', 'meta', 'tiktok']) {
|
|
721
|
+
assert.equal(outcomeFor(results, provider).startsWith('skipped (consent'), false, `${provider} fires without a snapshot`);
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
{
|
|
727
|
+
name: 'an-order-with-no-attribution-still-fires-on-external-id',
|
|
728
|
+
auth: 'none',
|
|
729
|
+
|
|
730
|
+
async run({ assert, ctx, Manager }) {
|
|
731
|
+
// The raw-API recovery lane: no browser was ever involved, so there is no
|
|
732
|
+
// attribution, no cookies and no request pair.
|
|
733
|
+
const results = deliver({
|
|
734
|
+
ctx,
|
|
735
|
+
Manager,
|
|
736
|
+
attribution: {},
|
|
737
|
+
identity: matchData.buildIdentity({ uid: UID }),
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const body = conversions.buildMetaBody({
|
|
741
|
+
descriptor: descriptorFor(results, 'meta'),
|
|
742
|
+
identity: matchData.buildIdentity({ uid: UID }),
|
|
743
|
+
eventId: 'purchase._test-webhook-event',
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
assert.deepEqual(body.data[0].user_data, { external_id: UID }, 'external_id alone — no empty match keys invented');
|
|
747
|
+
|
|
748
|
+
// The lane where external_id is the ONLY match key is exactly where its
|
|
749
|
+
// shape decides whether the fire matches anybody at all (#410).
|
|
750
|
+
const tiktok = conversions.buildTikTokBody({
|
|
751
|
+
descriptor: descriptorFor(results, 'tiktok'),
|
|
752
|
+
identity: matchData.buildIdentity({ uid: UID }),
|
|
753
|
+
eventId: 'purchase._test-webhook-event',
|
|
754
|
+
pixelCode: '_TEST_PIXEL',
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
assert.deepEqual(tiktok.data[0].user, { external_id: UID_DIGEST }, 'TikTok\'s lone match key is the digest its Events API requires');
|
|
758
|
+
|
|
759
|
+
assert.equal(descriptorFor(results, 'ga4').payload.source, undefined, 'no campaign params either');
|
|
760
|
+
assert.equal(outcomeFor(results, 'meta').startsWith('skipped (consent'), false, 'missing match data never blocks the fire');
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
{
|
|
765
|
+
// `sign_up` is a `placement: 'both'` event, and its two halves do NOT all
|
|
766
|
+
// deduplicate: Meta and TikTok merge on event_id, GA4 has no cross-source
|
|
767
|
+
// dedupe at all — a Measurement Protocol sign_up beside the browser's gtag
|
|
768
|
+
// one is simply two registrations. So the server half names the providers
|
|
769
|
+
// it owns, and GA4 stays mapped in the catalog for the client.
|
|
770
|
+
name: 'a-provider-the-other-half-owns-is-never-fired',
|
|
771
|
+
auth: 'none',
|
|
772
|
+
|
|
773
|
+
async run({ assert, ctx, Manager }) {
|
|
774
|
+
const results = deliver({
|
|
775
|
+
ctx,
|
|
776
|
+
Manager,
|
|
777
|
+
event: 'sign_up',
|
|
778
|
+
params: { method: 'email', user_id: UID },
|
|
779
|
+
providers: ['meta', 'tiktok'],
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
assert.equal(outcomeFor(results, 'ga4'), 'skipped (not selected)', 'GA4 must not be fired by a half that cannot dedupe against the other');
|
|
783
|
+
assert.equal(descriptorFor(results, 'ga4'), undefined, 'an unselected provider never even resolves its payload');
|
|
784
|
+
|
|
785
|
+
// The two that DO dedupe are resolved and handed to their transport.
|
|
786
|
+
for (const provider of ['meta', 'tiktok']) {
|
|
787
|
+
assert.equal(descriptorFor(results, provider).name, 'CompleteRegistration', `${provider} resolves its native event`);
|
|
788
|
+
assert.equal(outcomeFor(results, provider).startsWith('skipped (not selected)'), false, `${provider} is this half's to fire`);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Unrestricted stays unrestricted — the seam is opt-in per fire.
|
|
792
|
+
const everyProvider = deliver({ ctx, Manager, event: 'sign_up', params: { method: 'email', user_id: UID } });
|
|
793
|
+
|
|
794
|
+
assert.equal(outcomeFor(everyProvider, 'ga4').startsWith('skipped (not selected)'), false, 'a fire that names no providers reaches every mapped one');
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
{
|
|
799
|
+
name: 'an-account-with-no-phone-sends-no-phone-hash',
|
|
800
|
+
auth: 'none',
|
|
801
|
+
|
|
802
|
+
async run({ assert }) {
|
|
803
|
+
// The account schema stores the pair as NUMBERS defaulting to 0. Hashing
|
|
804
|
+
// that would give every phoneless account the same match key — junk the
|
|
805
|
+
// platforms would happily match strangers to each other on.
|
|
806
|
+
for (const hash of [matchData.hashPhone, matchData.hashPhoneE164]) {
|
|
807
|
+
assert.equal(hash({ countryCode: 0, national: 0 }), null, 'the schema default is no phone');
|
|
808
|
+
assert.equal(hash(undefined), null);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
assert.equal(matchData.hashPhone({ countryCode: 1, national: 5550102030 }), sha256('15550102030'), 'a real number still hashes');
|
|
812
|
+
|
|
813
|
+
// Firebase Auth hands the trigger an E.164 string instead of the pair.
|
|
814
|
+
assert.equal(matchData.hashPhone('+1 555-010-2030'), sha256('15550102030'), 'the Auth record shape hashes to the same digest');
|
|
815
|
+
|
|
816
|
+
const identity = matchData.buildIdentity({ uid: UID, telephone: { countryCode: 0, national: 0 } });
|
|
817
|
+
|
|
818
|
+
assert.equal(Object.hasOwn(identity, 'metaPhoneHash'), false, 'an unresolvable match key is absent, never empty');
|
|
819
|
+
assert.equal(Object.hasOwn(identity, 'tiktokPhoneHash'), false, 'and neither normalization invents one');
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
|
|
823
|
+
{
|
|
824
|
+
// ONE hash used to go to every provider, and the platforms do not agree on
|
|
825
|
+
// what to hash ([#392](https://github.com/Omega-JS-Stack/omega/issues/392)):
|
|
826
|
+
// Meta's advanced matching wants bare digits, TikTok's Events API the E.164
|
|
827
|
+
// form WITH the plus. Same person, two digests — an off-spec key does not
|
|
828
|
+
// error, it just silently matches nobody.
|
|
829
|
+
name: 'the-phone-hash-is-normalized-per-provider-spec',
|
|
830
|
+
auth: 'none',
|
|
831
|
+
|
|
832
|
+
async run({ assert, ctx, Manager }) {
|
|
833
|
+
const telephone = { countryCode: 1, national: 5550102030 };
|
|
834
|
+
const identity = matchData.buildIdentity({ uid: UID, telephone: telephone });
|
|
835
|
+
|
|
836
|
+
assert.equal(identity.metaPhoneHash, sha256('15550102030'), 'Meta hashes the bare digits');
|
|
837
|
+
assert.equal(identity.tiktokPhoneHash, sha256('+15550102030'), 'TikTok hashes the E.164 number');
|
|
838
|
+
assert.equal(identity.metaPhoneHash === identity.tiktokPhoneHash, false, 'two specs, two digests — never one hash for both');
|
|
839
|
+
|
|
840
|
+
// The same normalizers the browser half uses (`@omega.js/analytics/identity`),
|
|
841
|
+
// so a server conversion and a pixel event carry the SAME match key.
|
|
842
|
+
const results = deliver({ ctx, Manager, identity });
|
|
843
|
+
|
|
844
|
+
const meta = conversions.buildMetaBody({ descriptor: descriptorFor(results, 'meta'), identity: identity, eventId: 'purchase._test-webhook-event' });
|
|
845
|
+
const tiktok = conversions.buildTikTokBody({ descriptor: descriptorFor(results, 'tiktok'), identity: identity, eventId: 'purchase._test-webhook-event', pixelCode: '_TEST_PIXEL' });
|
|
846
|
+
|
|
847
|
+
assert.equal(meta.data[0].user_data.ph, sha256('15550102030'), 'the Meta body carries the digits-only digest');
|
|
848
|
+
assert.equal(tiktok.data[0].user.phone, sha256('+15550102030'), 'the TikTok body carries the E.164 digest');
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
|
|
852
|
+
{
|
|
853
|
+
name: 'an-event-name-the-catalog-does-not-know-is-loud-and-harmless',
|
|
854
|
+
auth: 'none',
|
|
855
|
+
|
|
856
|
+
async run({ assert, ctx, Manager }) {
|
|
857
|
+
// A typo in a canonical name used to be SILENT: every adapter returned
|
|
858
|
+
// null, and the fire read exactly like `subscription_plan_change` on Meta — a
|
|
859
|
+
// deliberate non-mapping. So a webhook could stop reporting revenue and the logs
|
|
860
|
+
// would say nothing was wrong. The outcome is now its own word, and the
|
|
861
|
+
// walk still cannot take the webhook down with it.
|
|
862
|
+
const warnings = [];
|
|
863
|
+
const record = (line) => warnings.push(String(line));
|
|
864
|
+
const guarded = { ...ctx, log: record, error: record, warn: record };
|
|
865
|
+
|
|
866
|
+
const results = conversions.deliverConversion({
|
|
867
|
+
event: 'purchsae',
|
|
868
|
+
params: PURCHASE_PARAMS,
|
|
869
|
+
eventId: 'purchsae._test-typo',
|
|
870
|
+
ctx: guarded,
|
|
871
|
+
Manager: Manager,
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
for (const provider of ['ga4', 'meta', 'tiktok']) {
|
|
875
|
+
assert.equal(outcomeFor(results, provider), 'skipped (unknown event)', `${provider} must say the NAME was wrong, not that the provider declined to map a real event`);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const loud = warnings.filter((line) => line.includes('[unknown canonical event]'));
|
|
879
|
+
|
|
880
|
+
assert.equal(loud.length, 1, `exactly one loud line, not one per provider: ${warnings.join(' | ')}`);
|
|
881
|
+
assert.equal(loud[0].includes('purchsae'), true, `the line must name the offender: ${loud[0]}`);
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
{
|
|
886
|
+
name: 'the-outcomes-an-ad-platform-has-no-use-for-reach-ga4-only',
|
|
887
|
+
auth: 'none',
|
|
888
|
+
|
|
889
|
+
async run({ assert, ctx, Manager }) {
|
|
890
|
+
// The catalog maps none of these on Meta or TikTok — an unmapped provider
|
|
891
|
+
// is a decision, and the adapter skips rather than inventing an event.
|
|
892
|
+
// `trial_lapse` is the deliberate omission of the exclusion lane below
|
|
893
|
+
// ([#415](https://github.com/Omega-JS-Stack/omega/issues/415)): a lapsed
|
|
894
|
+
// trialist is a win-back audience to retarget, not one to hide ads from.
|
|
895
|
+
// The other two are [#407](https://github.com/Omega-JS-Stack/omega/issues/407)'s
|
|
896
|
+
// lifecycle additions, dark to an ad platform because no money moved.
|
|
897
|
+
for (const event of ['subscription_uncancel', 'subscription_plan_change', 'trial_lapse']) {
|
|
898
|
+
const results = deliver({ ctx, Manager, event });
|
|
899
|
+
|
|
900
|
+
assert.equal(descriptorFor(results, 'ga4').name, event, `${event} resolves on GA4 under its own name`);
|
|
901
|
+
assert.equal(outcomeFor(results, 'meta'), 'skipped (no mapping)', `${event} has no Meta mapping`);
|
|
902
|
+
assert.equal(outcomeFor(results, 'tiktok'), 'skipped (no mapping)', `${event} has no TikTok mapping`);
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
{
|
|
908
|
+
name: 'a-cancellation-and-a-refund-build-exclusion-audiences-worth-zero',
|
|
909
|
+
auth: 'none',
|
|
910
|
+
|
|
911
|
+
async run({ assert, ctx, Manager }) {
|
|
912
|
+
// The churn moments an ad platform CAN use
|
|
913
|
+
// ([#415](https://github.com/Omega-JS-Stack/omega/issues/415)): neither
|
|
914
|
+
// Meta nor TikTok can subtract revenue and neither optimizes against an
|
|
915
|
+
// event, but both can build an exclusion audience from a custom one. So
|
|
916
|
+
// each webhook fire that already reaches GA4 now resolves on the two
|
|
917
|
+
// platforms as well — at value ZERO, because a refund carrying its
|
|
918
|
+
// amount would ADD to the return their ads manager reports.
|
|
919
|
+
for (const [event, native] of [['subscription_cancel', 'SubscriptionCancel'], ['refund', 'Refund']]) {
|
|
920
|
+
const results = deliver({ ctx, Manager, event });
|
|
921
|
+
|
|
922
|
+
const google = descriptorFor(results, 'ga4');
|
|
923
|
+
const meta = descriptorFor(results, 'meta');
|
|
924
|
+
const tiktok = descriptorFor(results, 'tiktok');
|
|
925
|
+
|
|
926
|
+
assert.equal(google.name, event, `${event} still resolves on GA4 under its own name`);
|
|
927
|
+
assert.equal(google.payload.value, 9.99, 'GA4 keeps the real number — its revenue report is where a refund nets out');
|
|
928
|
+
|
|
929
|
+
assert.equal(meta.name, native, `${event} reaches Meta as ${native}`);
|
|
930
|
+
assert.equal(meta.kind, 'custom', 'Meta defines no churn event, so the pixel command is trackCustom');
|
|
931
|
+
assert.equal(meta.payload.value, 0, 'a churn signal must never book revenue in an ad account');
|
|
932
|
+
assert.deepEqual(meta.payload.content_ids, ['premium'], 'the plan is what makes the audience worth building');
|
|
933
|
+
|
|
934
|
+
assert.equal(tiktok.name, native, `${event} reaches TikTok as ${native}`);
|
|
935
|
+
assert.equal(tiktok.kind, 'custom');
|
|
936
|
+
assert.equal(tiktok.payload.value, 0, 'TikTok cannot subtract revenue either');
|
|
937
|
+
assert.equal(tiktok.payload.contents[0].content_id, 'premium');
|
|
938
|
+
assert.equal(tiktok.payload.contents[0].price, undefined, 'the per-item price would put the amount back on the wire');
|
|
939
|
+
assert.equal(tiktok.payload.contents[0].quantity, undefined, 'so it comes off with the value, like Meta\'s signal');
|
|
940
|
+
|
|
941
|
+
// The bodies that would go on the wire carry the same zero.
|
|
942
|
+
const body = conversions.buildMetaBody({ descriptor: meta, identity: {}, eventId: `${event}._test-webhook-event` });
|
|
943
|
+
const tiktokBody = conversions.buildTikTokBody({ descriptor: tiktok, identity: {}, eventId: `${event}._test-webhook-event`, pixelCode: '_TEST_PIXEL' });
|
|
944
|
+
|
|
945
|
+
assert.equal(body.data[0].event_name, native);
|
|
946
|
+
assert.equal(body.data[0].custom_data.value, 0);
|
|
947
|
+
assert.equal(tiktokBody.data[0].event, native);
|
|
948
|
+
assert.equal(tiktokBody.data[0].properties.value, 0);
|
|
949
|
+
assert.equal(JSON.stringify(tiktokBody).includes('9.99'), false, 'no amount rides a churn body under ANY key');
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
},
|
|
953
|
+
|
|
954
|
+
{
|
|
955
|
+
name: 'a-trial-conversion-reaches-all-three-as-real-revenue',
|
|
956
|
+
auth: 'none',
|
|
957
|
+
|
|
958
|
+
async run({ assert, ctx, Manager }) {
|
|
959
|
+
// The one [#407] addition where money actually moved, so unlike the four
|
|
960
|
+
// above it carries a full mapping: GA4's standard purchase (revenue
|
|
961
|
+
// belongs in the revenue report) and each ad platform's Purchase, which
|
|
962
|
+
// is what every real charge is to them (Ian's money-event table, [#652]).
|
|
963
|
+
const results = deliver({
|
|
964
|
+
ctx,
|
|
965
|
+
Manager,
|
|
966
|
+
event: 'trial_convert',
|
|
967
|
+
params: { ...PURCHASE_PARAMS, is_trial: true, is_recurring: false },
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
const google = descriptorFor(results, 'ga4');
|
|
971
|
+
|
|
972
|
+
assert.equal(google.name, 'purchase', 'the first real charge lands in GA4 revenue');
|
|
973
|
+
assert.equal(google.payload.value, 9.99);
|
|
974
|
+
assert.equal(google.payload.is_trial, true, 'the flag pair is what marks it inside GA4 purchase');
|
|
975
|
+
assert.equal(google.payload.is_recurring, false);
|
|
976
|
+
|
|
977
|
+
assert.equal(descriptorFor(results, 'meta').name, 'Purchase');
|
|
978
|
+
assert.equal(descriptorFor(results, 'tiktok').name, 'Purchase');
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
|
|
982
|
+
{
|
|
983
|
+
// An emulator boot seeds personas, every seeded account fired the server
|
|
984
|
+
// half of `sign_up`, and Meta DELIVERED dozens of fake registrations to the
|
|
985
|
+
// brand's live pixel — real ad data polluted by a dev run
|
|
986
|
+
// ([#464](https://github.com/Omega-JS-Stack/omega/issues/464)). The gate is
|
|
987
|
+
// the one `@omega.js/monitoring` and the GA4 Measurement Protocol helper
|
|
988
|
+
// already use: outside production, nothing goes out.
|
|
989
|
+
name: 'a-dev-run-resolves-the-whole-walk-and-delivers-nothing',
|
|
990
|
+
auth: 'none',
|
|
991
|
+
|
|
992
|
+
async run({ assert }) {
|
|
993
|
+
const { results, calls, lines } = fireOnRecordedWire({ production: false });
|
|
994
|
+
|
|
995
|
+
assert.equal(calls.length, 0, `an emulator run must reach no platform: ${calls.map((call) => call.url).join(', ')}`);
|
|
996
|
+
|
|
997
|
+
for (const [provider, native] of [['meta', 'Purchase'], ['tiktok', 'Purchase']]) {
|
|
998
|
+
assert.equal(outcomeFor(results, provider), 'blocked (dev)', `${provider} says it was blocked, not that it sent`);
|
|
999
|
+
assert.equal(descriptorFor(results, provider).name, native, `${provider} still resolves — the dev trace names what WOULD have fired`);
|
|
1000
|
+
|
|
1001
|
+
const blocked = lines.filter((line) => line.includes(`[${provider}]`) && line.includes('blocked (dev)'));
|
|
1002
|
+
|
|
1003
|
+
assert.equal(blocked.length, 1, `exactly one blocked line for ${provider}: ${lines.join(' | ')}`);
|
|
1004
|
+
assert.equal(blocked[0].includes('purchase'), true, `the line names the event: ${blocked[0]}`);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// And the same fire in production is the proof the gate reads the
|
|
1008
|
+
// environment rather than simply never sending.
|
|
1009
|
+
assert.equal(fireOnRecordedWire({ production: true }).calls.length, 2, 'production still delivers to both platforms');
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
],
|
|
1013
|
+
});
|