working on tests
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -112,4 +112,5 @@ interview
|
||||
# But ignore server scripts folder
|
||||
/server/scripts/
|
||||
|
||||
credentials/
|
||||
credentials/
|
||||
server/chrome
|
||||
|
||||
141
bun.lock
141
bun.lock
@@ -25,6 +25,7 @@
|
||||
"@autumn/shared": "workspace:*",
|
||||
"chalk": "^5.3.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"inquirer": "^12.6.3",
|
||||
"ora": "^9.0.0",
|
||||
"p-limit": "^7.2.0",
|
||||
@@ -98,6 +99,8 @@
|
||||
"hono": "^4.9.9",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"http-status-codes": "^2.3.0",
|
||||
"ink": "^6.3.1",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ioredis": "^5.5.0",
|
||||
"ksuid": "^3.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -105,6 +108,7 @@
|
||||
"mime-detect": "^1.3.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"openai": "^4.85.2",
|
||||
"p-limit": "^7.2.0",
|
||||
"pg": "^8.13.1",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
@@ -123,6 +127,7 @@
|
||||
"zod": "^3.25.23",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/chai": "^5.0.1",
|
||||
"@types/chai-http": "^3.0.5",
|
||||
"@types/cors": "^2.8.19",
|
||||
@@ -280,6 +285,8 @@
|
||||
|
||||
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
|
||||
|
||||
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-mkOh+Wwawzuf5wa30bvc4nA+Qb6DIrGWgBhRR/Pw4T9nsgYait8izvXkNyU78D6Wcu3Z+KUdwCmLCxlWjEotYA=="],
|
||||
|
||||
"@amplitude/analytics-browser": ["@amplitude/analytics-browser@2.27.0", "", { "dependencies": { "@amplitude/analytics-core": "^2.28.0", "@amplitude/plugin-autocapture-browser": "^1.15.3", "@amplitude/plugin-network-capture-browser": "^1.6.9", "@amplitude/plugin-page-view-tracking-browser": "^2.5.3", "@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.31", "tslib": "^2.4.1" } }, "sha512-1LBCLmnr7aUpLtOp64lpr8GzN3vPKM0fwiM/7tWJ9XU9/GKA+k3CUSjI8OdERKrw2yVywujoAVQo4anGZXYIDA=="],
|
||||
|
||||
"@amplitude/analytics-client-common": ["@amplitude/analytics-client-common@2.4.8", "", { "dependencies": { "@amplitude/analytics-connector": "^1.4.8", "@amplitude/analytics-core": "^2.28.0", "@amplitude/analytics-types": "^2.10.0", "tslib": "^2.4.1" } }, "sha512-cSm9Q+qcLy65kV2MnD7WlKrFMDOkj14dHM2YQn5njUrSXQljyjtYCss+HVzgIlWPSDgA5v6dDeHwxzZl3VSggw=="],
|
||||
@@ -1608,9 +1615,11 @@
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.1.1", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
@@ -1640,6 +1649,8 @@
|
||||
|
||||
"attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
|
||||
|
||||
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
||||
|
||||
"autumn-js": ["autumn-js@0.1.40", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call"] }, "sha512-nAmyFJLOQqKosb8MHv09rB2pma8LyOHWsuYtrjXND+2LM51vToco1mweLIYIs/aX33iLAVUxfpXEEt8P3UYoxw=="],
|
||||
|
||||
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
|
||||
@@ -1754,10 +1765,14 @@
|
||||
|
||||
"clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="],
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="],
|
||||
|
||||
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||
|
||||
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
@@ -1776,6 +1791,8 @@
|
||||
|
||||
"cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="],
|
||||
|
||||
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
@@ -1802,6 +1819,8 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
||||
|
||||
"convex": ["convex@1.28.0", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-40FgeJ/LxP9TxnkDDztU/A5gcGTdq1klcTT5mM0Ak+kSlQiDktMpjNX1TfkWLxXaE3lI4qvawKH95v2RiYgFxA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
|
||||
@@ -1942,7 +1961,7 @@
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
|
||||
"drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="],
|
||||
|
||||
"drizzle-zod": ["drizzle-zod@0.8.3", "", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww=="],
|
||||
|
||||
@@ -1954,7 +1973,7 @@
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.237", "", {}, "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
@@ -1968,6 +1987,8 @@
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||
|
||||
"error-causes": ["error-causes@3.0.2", "", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
@@ -2226,10 +2247,16 @@
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
||||
"ink": ["ink@6.3.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-3wGwITGrzL6rkWsi2gEKzgwdafGn4ZYd3u4oRp+sOPvfoxEHlnoB5Vnk9Uy5dMRUhDOqF3hqr4rLQ4lEzBc2sQ=="],
|
||||
|
||||
"ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="],
|
||||
|
||||
"input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="],
|
||||
|
||||
"inquirer": ["inquirer@12.10.0", "", { "dependencies": { "@inquirer/ansi": "^1.0.1", "@inquirer/core": "^10.3.0", "@inquirer/prompts": "^7.9.0", "@inquirer/type": "^3.0.9", "mute-stream": "^2.0.0", "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-K/epfEnDBZj2Q3NMDcgXWZye3nhSPeoJnOh8lcKWrldw54UEZfS4EmAMsAsmVbl7qKi+vjAsy39Sz4fbgRMewg=="],
|
||||
@@ -2252,12 +2279,14 @@
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-in-browser": ["is-in-browser@1.1.3", "", {}, "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g=="],
|
||||
|
||||
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
|
||||
|
||||
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
||||
|
||||
"is-ip": ["is-ip@5.0.1", "", { "dependencies": { "ip-regex": "^5.0.0", "super-regex": "^0.2.0" } }, "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw=="],
|
||||
@@ -2568,6 +2597,8 @@
|
||||
|
||||
"pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
|
||||
|
||||
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
@@ -2720,6 +2751,8 @@
|
||||
|
||||
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
|
||||
|
||||
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
||||
|
||||
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
@@ -2832,7 +2865,7 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||
|
||||
@@ -2842,6 +2875,8 @@
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
|
||||
@@ -2872,6 +2907,8 @@
|
||||
|
||||
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||
|
||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="],
|
||||
@@ -2988,7 +3025,7 @@
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
|
||||
@@ -3068,13 +3105,15 @@
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
|
||||
|
||||
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
@@ -3106,6 +3145,8 @@
|
||||
|
||||
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
|
||||
|
||||
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zod-openapi": ["zod-openapi@5.4.3", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-6kJ/gJdvHZtuxjYHoMtkl2PixCwRuZ/s79dVkEr7arHvZGXfx7Cvh53X3HfJ5h9FzGelXOXlnyjwfX0sKEPByw=="],
|
||||
@@ -3140,6 +3181,10 @@
|
||||
|
||||
"@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||
|
||||
"@autumn/server/drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
|
||||
|
||||
"@autumn/shared/drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
|
||||
|
||||
"@autumn/vite/@types/node": ["@types/node@22.18.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ=="],
|
||||
|
||||
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
|
||||
@@ -3532,6 +3577,10 @@
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
|
||||
|
||||
"@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||
|
||||
"@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
@@ -3936,6 +3985,10 @@
|
||||
|
||||
"bun-types/@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="],
|
||||
|
||||
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
||||
|
||||
"cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -3972,12 +4025,20 @@
|
||||
|
||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"html-minifier-terser/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||
|
||||
"ink/cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
|
||||
|
||||
"ink/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"ink-spinner/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"mocha/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
@@ -4018,6 +4079,8 @@
|
||||
|
||||
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
|
||||
|
||||
"react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"react-router/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"recaseai/@types/node": ["@types/node@22.18.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ=="],
|
||||
@@ -4030,12 +4093,16 @@
|
||||
|
||||
"renderkid/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
|
||||
|
||||
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"serialize-error/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"socket.io-adapter/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
@@ -4044,6 +4111,12 @@
|
||||
|
||||
"socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -4062,11 +4135,13 @@
|
||||
|
||||
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"winston-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
@@ -4536,9 +4611,13 @@
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
"@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
"@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.202.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw=="],
|
||||
|
||||
@@ -4700,10 +4779,20 @@
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
|
||||
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
|
||||
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"cloudflare/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"concurrently/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||
@@ -4760,6 +4849,8 @@
|
||||
|
||||
"engine.io/@types/node/undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
|
||||
"eslint/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"eslint/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"eslint/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
@@ -4770,6 +4861,8 @@
|
||||
|
||||
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"ink/cli-cursor/restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
||||
|
||||
"mocha/log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"mocha/log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
@@ -4832,9 +4925,15 @@
|
||||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
@@ -5060,12 +5159,20 @@
|
||||
|
||||
"@hyperdx/node-opentelemetry/@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node/@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg=="],
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
||||
@@ -5080,6 +5187,10 @@
|
||||
|
||||
"css-select/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
|
||||
|
||||
"ink/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"mocha/log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"mocha/log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"nodemon/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
@@ -5090,12 +5201,8 @@
|
||||
|
||||
"react-email/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
|
||||
|
||||
"react-email/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"recaseai/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
|
||||
|
||||
"recaseai/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"renderkid/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],
|
||||
|
||||
"tsc-alias/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
@@ -5188,8 +5295,6 @@
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"@hyperdx/node-opentelemetry/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"@sentry/node/@opentelemetry/instrumentation-mysql/@types/mysql/@types/node/undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
|
||||
"@sentry/node/@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@autumn/shared": "workspace:*",
|
||||
"chalk": "^5.3.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"inquirer": "^12.6.3",
|
||||
"ora": "^9.0.0",
|
||||
"p-limit": "^7.2.0"
|
||||
|
||||
@@ -21,6 +21,7 @@ fi
|
||||
# 'server/tests/balances/track/legacy' \
|
||||
# 'server/tests/balances/check/basic' \
|
||||
# 'server/tests/balances/check/credit-systems' \
|
||||
# 'server/tests/balances/check/misc' \
|
||||
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/attach/basic' \
|
||||
|
||||
@@ -12,17 +12,15 @@ if [[ "$1" == *"setup"* ]]; then
|
||||
BUN_SETUP
|
||||
fi
|
||||
|
||||
# These tests still use Mocha - will be migrated later
|
||||
|
||||
MOCHA_CMD \
|
||||
'tests/attach/migrations/*.ts' \
|
||||
'tests/attach/newVersion/*.ts' \
|
||||
'tests/attach/upgradeOld/*.ts' \
|
||||
'tests/attach/others/*.ts' \
|
||||
'tests/attach/updateEnts/*.ts' \
|
||||
'tests/advanced/check/*.ts'
|
||||
|
||||
MOCHA_CMD 'tests/attach/prepaid/*.ts' \
|
||||
'tests/interval/upgrade/*.ts' \
|
||||
'tests/interval/multiSub/*.ts'
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/attach/migrations' \
|
||||
'server/tests/attach/others' \
|
||||
# 'server/tests/attach/newVersion' \
|
||||
# 'server/tests/attach/upgradeOld' \
|
||||
# 'server/tests/attach/updateEnts' \
|
||||
# 'server/tests/advanced/check' \
|
||||
# 'server/tests/attach/prepaid' \
|
||||
# 'server/tests/interval/upgrade' \
|
||||
# 'server/tests/interval/multiSub' \
|
||||
# --max=6
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ if [[ "$1" == *"setup"* ]]; then
|
||||
BUN_SETUP
|
||||
fi
|
||||
|
||||
# These tests still use Mocha - will be migrated later
|
||||
|
||||
MOCHA_CMD 'tests/contUse/entities/*.ts'
|
||||
MOCHA_CMD 'tests/contUse/update/*.ts'
|
||||
MOCHA_CMD 'tests/contUse/track/*.ts'
|
||||
MOCHA_CMD 'tests/contUse/roles/*.ts'
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/contUse/entities' \
|
||||
'server/tests/contUse/update' \
|
||||
'server/tests/contUse/track' \
|
||||
'server/tests/contUse/roles' \
|
||||
--max=6
|
||||
|
||||
|
||||
@@ -12,20 +12,18 @@ if [[ "$1" == *"setup"* ]]; then
|
||||
BUN_SETUP
|
||||
fi
|
||||
|
||||
# These tests still use Mocha - will be migrated later
|
||||
|
||||
MOCHA_CMD 'tests/merged/group/*.ts'
|
||||
|
||||
MOCHA_CMD 'tests/merged/add/*.ts' \
|
||||
'tests/merged/downgrade/*.ts' \
|
||||
'tests/merged/prepaid/*.ts' \
|
||||
'tests/merged/separate/*.ts' \
|
||||
'tests/merged/upgrade/*.ts' \
|
||||
'tests/merged/trial/*.ts'
|
||||
|
||||
MOCHA_CMD 'tests/merged/addOn/*.ts' \
|
||||
'tests/core/cancel/*.ts' \
|
||||
'tests/core/multiAttach/*.ts' \
|
||||
'tests/core/multiAttach/multiInvoice/*.ts' \
|
||||
'tests/core/multiAttach/multiUpgrade/*.ts'
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/merged/group' \
|
||||
'server/tests/merged/add' \
|
||||
'server/tests/merged/downgrade' \
|
||||
'server/tests/merged/prepaid' \
|
||||
'server/tests/merged/separate' \
|
||||
'server/tests/merged/upgrade' \
|
||||
'server/tests/merged/trial' \
|
||||
'server/tests/merged/addOn' \
|
||||
'server/tests/core/cancel' \
|
||||
'server/tests/core/multiAttach' \
|
||||
'server/tests/core/multiAttach/multiInvoice' \
|
||||
'server/tests/core/multiAttach/multiUpgrade' \
|
||||
--max=6
|
||||
|
||||
|
||||
@@ -12,18 +12,15 @@ if [[ "$1" == *"setup"* ]]; then
|
||||
BUN_SETUP
|
||||
fi
|
||||
|
||||
# These tests still use Mocha - will be migrated later
|
||||
# Note: advanced/multiFeature, advanced/rollovers, advanced/customInterval,
|
||||
# advanced/usageLimit still use Mocha (not migrated yet)
|
||||
|
||||
MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
|
||||
'tests/advanced/coupons/*.ts' \
|
||||
'tests/attach/updateQuantity/*.ts' \
|
||||
'tests/advanced/referrals/*.ts' \
|
||||
'tests/advanced/referrals/paid/*.ts' \
|
||||
'tests/advanced/rollovers/*.ts' \
|
||||
'tests/advanced/customInterval/*.ts'
|
||||
|
||||
MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
'tests/advanced/usageLimit/*.ts'
|
||||
|
||||
MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/advanced/coupons' \
|
||||
'server/tests/attach/updateQuantity' \
|
||||
'server/tests/advanced/referrals' \
|
||||
'server/tests/advanced/referrals/paid' \
|
||||
'server/tests/attach/multiProduct' \
|
||||
'server/tests/advanced/usage' \
|
||||
--max=6
|
||||
|
||||
|
||||
42
server/checkFeatures.ts
Normal file
42
server/checkFeatures.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { initDrizzle } from "@/db/initDrizzle.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { StripeAccountService } from "@/internal/stripe/StripeAccountService.js";
|
||||
|
||||
const orgSlug = process.env.TESTS_ORG || "test-debug|org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt";
|
||||
|
||||
async function main() {
|
||||
const { db, client } = initDrizzle();
|
||||
|
||||
const org = await OrgService.getBySlug({ db, slug: orgSlug });
|
||||
console.log("\n=== ORG ===");
|
||||
console.log("ID:", org.id);
|
||||
console.log("Slug:", org.slug);
|
||||
console.log("Name:", org.name);
|
||||
console.log("\n=== STRIPE CONFIG ===");
|
||||
console.log("test_stripe_connect:", org.test_stripe_connect);
|
||||
console.log("live_stripe_connect:", org.live_stripe_connect);
|
||||
|
||||
const features = await FeatureService.list({
|
||||
db,
|
||||
orgId: org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
});
|
||||
|
||||
console.log("\n=== FEATURES ===");
|
||||
console.log("Total features:", features.length);
|
||||
for (const feature of features) {
|
||||
console.log(`- ${feature.id} (${feature.type})`, feature.usage_type ? `usage_type: ${feature.usage_type}` : '');
|
||||
if (feature.id === 'messages') {
|
||||
console.log("\n Full messages feature:", JSON.stringify(feature, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
await client.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -13,7 +13,13 @@
|
||||
"cron": "bun src/cron.ts",
|
||||
"check": "bun src/check.ts",
|
||||
"build": "bun build ./src/index.ts ./src/workers.ts ./src/cron.ts --outdir dist --target bun",
|
||||
"build:check": "tsc -b tsconfig.build.json"
|
||||
"build:check": "tsc -b tsconfig.build.json",
|
||||
"t": "bun tests/testRunner/runParallelGroupsV3.ts",
|
||||
"parallel-tests": "bun tests/testRunner/runParallelGroupsV3.ts",
|
||||
"parallel-tests:v1": "bun tests/testRunner/runParallelGroups.ts",
|
||||
"parallel-tests:verbose": "bun tests/testRunner/runParallelGroups.ts --verbose",
|
||||
"parallel-tests:debug": "bun tests/testRunner/runParallelGroups.ts --debug",
|
||||
"clear-master": "bun tests/clearMasterOrg.ts"
|
||||
},
|
||||
"mocha": {
|
||||
"node-option": [
|
||||
@@ -82,6 +88,8 @@
|
||||
"hono": "^4.9.9",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"http-status-codes": "^2.3.0",
|
||||
"ink": "^6.3.1",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ioredis": "^5.5.0",
|
||||
"ksuid": "^3.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -89,6 +97,7 @@
|
||||
"mime-detect": "^1.3.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"openai": "^4.85.2",
|
||||
"p-limit": "^7.2.0",
|
||||
"pg": "^8.13.1",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
@@ -107,6 +116,7 @@
|
||||
"zod": "^3.25.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/chai": "^5.0.1",
|
||||
"@types/chai-http": "^3.0.5",
|
||||
"@types/cors": "^2.8.19",
|
||||
|
||||
@@ -22,11 +22,11 @@ BUN_SETUP="$BUN_CMD tests/setupMain.ts"
|
||||
|
||||
# Test runner functions (using new TypeScript runner)
|
||||
BUN_PARALLEL() {
|
||||
cd "$PROJECT_ROOT" && $BUN_CMD scripts/testScripts/runTests.ts "$@"
|
||||
cd "$PROJECT_ROOT" && $BUN_CMD server/tests/testRunner/runTests.ts "$@"
|
||||
}
|
||||
|
||||
BUN_PARALLEL_COMPACT() {
|
||||
cd "$PROJECT_ROOT" && $BUN_CMD scripts/testScripts/runTests.ts "$@" --compact
|
||||
cd "$PROJECT_ROOT" && $BUN_CMD server/tests/testRunner/runTests.ts "$@" --compact
|
||||
}
|
||||
|
||||
# Mocha command (for tests not yet migrated)
|
||||
|
||||
24
server/shell/parallel.sh
Executable file
24
server/shell/parallel.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Parallel Test Runner
|
||||
# Runs all test groups in parallel, each with its own dedicated org
|
||||
|
||||
# Source shared configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
|
||||
# Check for required environment variables
|
||||
if [ -z "$TEST_ORG_SECRET_KEY" ]; then
|
||||
echo "Error: TEST_ORG_SECRET_KEY environment variable is required"
|
||||
echo ""
|
||||
echo "This should be the secret key of your platform organization"
|
||||
echo "that has access to create/delete test organizations."
|
||||
echo ""
|
||||
echo "Add it to your server/.env file:"
|
||||
echo " TEST_ORG_SECRET_KEY=am_sk_test_..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run parallel test groups
|
||||
echo "Starting parallel test runner..."
|
||||
cd "$PROJECT_ROOT" && $BUN_CMD server/tests/testRunner/runParallelGroups.ts
|
||||
3
server/src/external/autumn/autumnCli.ts
vendored
3
server/src/external/autumn/autumnCli.ts
vendored
@@ -24,6 +24,7 @@ import type {
|
||||
Customer,
|
||||
UsageParams,
|
||||
} from "autumn-js";
|
||||
import { defaultApiVersion } from "tests/constants.js";
|
||||
|
||||
export default class AutumnError extends Error {
|
||||
message: string;
|
||||
@@ -70,7 +71,7 @@ export class AutumnInt {
|
||||
};
|
||||
|
||||
if (version) {
|
||||
this.headers["x-api-version"] = version.toString();
|
||||
this.headers["x-api-version"] = version.toString() || defaultApiVersion;
|
||||
}
|
||||
|
||||
if (orgConfig) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { unsetOrgStripeKeys } from "@/internal/orgs/orgUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../honoUtils/HonoEnv.js";
|
||||
import { deleteCachedApiCustomer } from "../../internal/customers/cusUtils/apiCusCacheUtils/deleteCachedApiCustomer.js";
|
||||
import { setCachedApiCusProducts } from "../../internal/customers/cusUtils/apiCusCacheUtils/setCachedApiCusProducts.js";
|
||||
import type { Logger } from "../logtail/logtailUtils.js";
|
||||
import { handleCheckoutSessionCompleted } from "./webhookHandlers/handleCheckoutCompleted.js";
|
||||
import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
|
||||
@@ -82,32 +80,32 @@ const handleStripeWebhookRefresh = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateProductEvents.includes(eventType)) {
|
||||
const fullCus = await CusService.getFull({
|
||||
db,
|
||||
idOrInternalId: cus.id!,
|
||||
orgId: org.id,
|
||||
env,
|
||||
withEntities: true,
|
||||
withSubs: true,
|
||||
});
|
||||
// if (updateProductEvents.includes(eventType)) {
|
||||
// const fullCus = await CusService.getFull({
|
||||
// db,
|
||||
// idOrInternalId: cus.id!,
|
||||
// orgId: org.id,
|
||||
// env,
|
||||
// withEntities: true,
|
||||
// withSubs: true,
|
||||
// });
|
||||
|
||||
await setCachedApiCusProducts({
|
||||
ctx,
|
||||
fullCus,
|
||||
customerId: cus.id!,
|
||||
});
|
||||
} else {
|
||||
await deleteCachedApiCustomer({
|
||||
customerId: cus.id!,
|
||||
orgId: org.id,
|
||||
env,
|
||||
source: {
|
||||
stripeWebhook: true,
|
||||
eventType,
|
||||
},
|
||||
});
|
||||
}
|
||||
// await setCachedApiCusProducts({
|
||||
// ctx,
|
||||
// fullCus,
|
||||
// customerId: cus.id!,
|
||||
// });
|
||||
// } else {
|
||||
// await deleteCachedApiCustomer({
|
||||
// customerId: cus.id!,
|
||||
// orgId: org.id,
|
||||
// env,
|
||||
// source: {
|
||||
// stripeWebhook: true,
|
||||
// eventType,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -125,7 +125,10 @@ export const handleTrack = createRoute({
|
||||
},
|
||||
});
|
||||
|
||||
if (error) code = "insufficient_balance";
|
||||
if (error) {
|
||||
console.log(`Redis deduction error: ${error}`);
|
||||
code = "insufficient_balance";
|
||||
}
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
|
||||
@@ -79,6 +79,7 @@ export const runRedisDeduction = async ({
|
||||
}
|
||||
|
||||
// Redis deduction successful: queue sync jobs and event insertion
|
||||
|
||||
if (result.success) {
|
||||
for (const deduction of featureDeductions) {
|
||||
globalSyncBatchingManager.addSyncPair({
|
||||
|
||||
@@ -96,7 +96,9 @@ export class SyncBatchingManager {
|
||||
*/
|
||||
private async executeCustomerBatch({
|
||||
customerId,
|
||||
}: { customerId: string }): Promise<void> {
|
||||
}: {
|
||||
customerId: string;
|
||||
}): Promise<void> {
|
||||
const customerBatch = this.customerBatches.get(customerId);
|
||||
if (!customerBatch) return;
|
||||
|
||||
@@ -167,7 +169,9 @@ export class SyncBatchingManager {
|
||||
async flush(): Promise<void> {
|
||||
const customerIds = Array.from(this.customerBatches.keys());
|
||||
await Promise.all(
|
||||
customerIds.map((customerId) => this.executeCustomerBatch({ customerId })),
|
||||
customerIds.map((customerId) =>
|
||||
this.executeCustomerBatch({ customerId }),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { type Feature, FeatureNotFoundError } from "@autumn/shared";
|
||||
import {
|
||||
type Feature,
|
||||
FeatureNotFoundError,
|
||||
RecaseError,
|
||||
} from "@autumn/shared";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import { getCreditSystemsFromFeature } from "../../../features/creditSystemUtils.js";
|
||||
|
||||
@@ -80,5 +84,11 @@ export const getTrackEventNameDeductions = ({
|
||||
}),
|
||||
);
|
||||
|
||||
if (featureDeductions.length === 0) {
|
||||
throw new RecaseError({
|
||||
message: `No features found for event name: ${eventName}`,
|
||||
});
|
||||
}
|
||||
|
||||
return featureDeductions;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type CreateCustomerParams,
|
||||
CusExpand,
|
||||
CusProductStatus,
|
||||
type CustomerData,
|
||||
type Entity,
|
||||
type EntityData,
|
||||
type FullCustomer,
|
||||
@@ -33,7 +33,7 @@ export const getOrCreateCustomer = async ({
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
customerId: string | null;
|
||||
customerData?: CustomerData;
|
||||
customerData?: CreateCustomerParams;
|
||||
inStatuses?: CusProductStatus[];
|
||||
skipGet?: boolean;
|
||||
withEntities?: boolean;
|
||||
@@ -89,7 +89,9 @@ export const getOrCreateCustomer = async ({
|
||||
fingerprint: customerData?.fingerprint,
|
||||
metadata: customerData?.metadata || {},
|
||||
stripe_id: customerData?.stripe_id,
|
||||
default_product_id: customerData?.default_product_id,
|
||||
},
|
||||
createDefaultProducts: customerData?.disable_default !== true,
|
||||
})) as FullCustomer;
|
||||
|
||||
customer = await CusService.getFull({
|
||||
|
||||
95
server/src/internal/orgs/orgUtils/deleteOrgUtils.ts
Normal file
95
server/src/internal/orgs/orgUtils/deleteOrgUtils.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
||||
import {
|
||||
deauthorizeAccount,
|
||||
deleteConnectedAccount,
|
||||
} from "@/external/connect/connectUtils.js";
|
||||
import { deleteSvixApp } from "@/external/svix/svixHelpers.js";
|
||||
import { deleteStripeWebhook } from "../orgUtils.js";
|
||||
|
||||
export const deleteSvixWebhooks = async ({
|
||||
org,
|
||||
logger,
|
||||
}: {
|
||||
org: Organization;
|
||||
logger: Logger;
|
||||
}) => {
|
||||
const batch = [];
|
||||
if (org.svix_config?.sandbox_app_id) {
|
||||
batch.push(
|
||||
deleteSvixApp({
|
||||
appId: org.svix_config.sandbox_app_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (org.svix_config?.live_app_id) {
|
||||
batch.push(
|
||||
deleteSvixApp({
|
||||
appId: org.svix_config.live_app_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(batch);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete svix webhooks for ${org.id}, ${org.slug}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteStripeWebhooks = async ({
|
||||
org,
|
||||
logger,
|
||||
}: {
|
||||
org: Organization;
|
||||
logger: Logger;
|
||||
}) => {
|
||||
try {
|
||||
await deleteStripeWebhook({
|
||||
org: org,
|
||||
env: AppEnv.Sandbox,
|
||||
});
|
||||
|
||||
await deleteStripeWebhook({
|
||||
org: org,
|
||||
env: AppEnv.Live,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Failed to delete stripe webhooks for ${org.id}, ${org.slug}. ${error.message})`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteStripeAccounts = async ({
|
||||
org,
|
||||
logger,
|
||||
}: {
|
||||
org: Organization;
|
||||
logger: Logger;
|
||||
}) => {
|
||||
if (org.test_stripe_connect?.account_id) {
|
||||
await deauthorizeAccount({
|
||||
accountId: org.test_stripe_connect.account_id,
|
||||
env: AppEnv.Sandbox,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
if (org.live_stripe_connect?.account_id) {
|
||||
await deauthorizeAccount({
|
||||
accountId: org.live_stripe_connect.account_id,
|
||||
env: AppEnv.Live,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
if (org.test_stripe_connect?.default_account_id) {
|
||||
await deleteConnectedAccount({
|
||||
accountId: org.test_stripe_connect.default_account_id,
|
||||
env: AppEnv.Sandbox,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -163,6 +163,7 @@ export const handleCreatePlatformOrg = createRoute({
|
||||
return c.json({
|
||||
test_secret_key,
|
||||
live_secret_key,
|
||||
org_slug: org.slug,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
AppEnv,
|
||||
customers,
|
||||
ErrCode,
|
||||
RecaseError,
|
||||
organizations,
|
||||
member,
|
||||
} from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { z } from "zod/v4";
|
||||
import type { Context } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import {
|
||||
deleteStripeAccounts,
|
||||
deleteSvixWebhooks,
|
||||
deleteStripeWebhooks,
|
||||
} from "@/internal/orgs/orgUtils/deleteOrgUtils.js";
|
||||
|
||||
const deleteOrgSchema = z.object({
|
||||
slug: z.string().min(1, "Organization slug is required"),
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /organizations
|
||||
* Deletes a platform organization by slug (for test cleanup)
|
||||
*/
|
||||
export const handleDeletePlatformOrg = [
|
||||
zValidator("json", deleteOrgSchema),
|
||||
async (c: Context<HonoEnv>) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db, logger, org: masterOrg } = ctx;
|
||||
|
||||
const { slug } = c.req.valid("json");
|
||||
|
||||
// Platform API creates orgs with format: {slug}|{masterOrgId}
|
||||
// So we need to find the org with this pattern
|
||||
const fullSlug = `${slug}|${masterOrg.id}`;
|
||||
|
||||
const org = await OrgService.getBySlug({ db, slug: fullSlug });
|
||||
if (!org) {
|
||||
throw new RecaseError({
|
||||
message: `Organization with slug "${slug}" not found`,
|
||||
code: ErrCode.NotFound,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
// Check if any live customers exist
|
||||
const hasCustomers = await db.query.customers.findFirst({
|
||||
where: and(eq(customers.org_id, org.id), eq(customers.env, AppEnv.Live)),
|
||||
});
|
||||
|
||||
if (hasCustomers) {
|
||||
throw new RecaseError({
|
||||
message: "Cannot delete org with production mode customers",
|
||||
code: ErrCode.OrgHasCustomers,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// Delete svix webhooks
|
||||
logger.info("1. Deleting svix webhooks");
|
||||
await deleteSvixWebhooks({ org, logger });
|
||||
|
||||
// Delete stripe webhooks
|
||||
logger.info("2. Deleting stripe webhooks");
|
||||
await deleteStripeWebhooks({ org, logger });
|
||||
|
||||
// Delete stripe accounts
|
||||
logger.info("3. Deleting stripe accounts");
|
||||
await deleteStripeAccounts({ org, logger });
|
||||
|
||||
// Delete all sandbox customers
|
||||
logger.info("4. Deleting sandbox customers");
|
||||
await db
|
||||
.delete(customers)
|
||||
.where(
|
||||
and(eq(customers.org_id, org.id), eq(customers.env, AppEnv.Sandbox)),
|
||||
);
|
||||
|
||||
// Delete memberships
|
||||
logger.info("5. Deleting org memberships");
|
||||
await db.delete(member).where(eq(member.organizationId, org.id));
|
||||
|
||||
// Delete the organization itself
|
||||
logger.info("6. Deleting organization");
|
||||
await db.delete(organizations).where(eq(organizations.id, org.id));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: `Organization "${slug}" deleted successfully`,
|
||||
});
|
||||
},
|
||||
];
|
||||
@@ -2,6 +2,7 @@ import { Autumn } from "autumn-js";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { handleCreatePlatformOrg } from "./handlers/handleCreatePlatformOrg.js";
|
||||
import { handleDeletePlatformOrg } from "./handlers/handleDeletePlatformOrg.js";
|
||||
import { handleGetPlatformOAuth } from "./handlers/handleGetPlatformOAuth.js";
|
||||
import { handleListPlatformOrgs } from "./handlers/handleListPlatformOrgs.js";
|
||||
import { listPlatformUsers } from "./handlers/handleListPlatformUsers.js";
|
||||
@@ -86,4 +87,11 @@ platformBetaRouter.post(
|
||||
platformBetaRouter.get("/users", ...listPlatformUsers);
|
||||
|
||||
platformBetaRouter.get("/organizations", ...handleListPlatformOrgs);
|
||||
|
||||
/**
|
||||
* DELETE /organizations
|
||||
* Deletes a platform organization by slug
|
||||
*/
|
||||
platformBetaRouter.delete("/organizations", ...handleDeletePlatformOrg);
|
||||
|
||||
export { platformBetaRouter };
|
||||
|
||||
@@ -118,11 +118,13 @@ export class ProductService {
|
||||
orgId,
|
||||
env,
|
||||
group,
|
||||
inIds,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
group?: string;
|
||||
inIds?: string[];
|
||||
}) {
|
||||
const prods = (await db.query.products.findMany({
|
||||
where: and(
|
||||
@@ -131,6 +133,7 @@ export class ProductService {
|
||||
eq(products.is_default, true),
|
||||
ne(products.archived, true),
|
||||
group ? eq(products.group, group) : undefined,
|
||||
inIds ? inArray(products.id, inIds) : undefined,
|
||||
),
|
||||
with: {
|
||||
entitlements: {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import type {
|
||||
type CreateFreeTrial,
|
||||
Entitlement,
|
||||
Feature,
|
||||
Price,
|
||||
ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import { itemToPriceAndEnt } from "@/internal/products/product-items/productItemUtils/itemToPriceAndEnt.js";
|
||||
|
||||
/**
|
||||
* V1 product format with entitlements as a record (used in tests)
|
||||
* Different from FullProduct which has entitlements as an array
|
||||
*/
|
||||
export type ProductV1 = {
|
||||
id: string;
|
||||
name: string;
|
||||
is_default: boolean;
|
||||
is_add_on: boolean;
|
||||
entitlements: Record<string, Entitlement>;
|
||||
prices: Price[];
|
||||
free_trial: CreateFreeTrial | null;
|
||||
group: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts ProductV2 (items-based) to V1 format (entitlements + prices)
|
||||
*
|
||||
* Uses production conversion utilities to ensure test expectations match actual behavior.
|
||||
*
|
||||
* @param productV2 - V2 product with items array
|
||||
* @param orgId - Organization ID
|
||||
* @param features - Available features in the org
|
||||
* @returns V1-format product object with entitlements and prices
|
||||
*/
|
||||
export const convertProductV2ToV1 = ({
|
||||
productV2,
|
||||
orgId,
|
||||
features,
|
||||
}: {
|
||||
productV2: ProductV2;
|
||||
orgId: string;
|
||||
features: Feature[];
|
||||
}): ProductV1 => {
|
||||
const entitlements: Entitlement[] = [];
|
||||
const prices: Price[] = [];
|
||||
|
||||
for (const item of productV2.items) {
|
||||
const feature = features.find((f) => f.id === item.feature_id);
|
||||
|
||||
// Use production conversion utilities
|
||||
const { newEnt, newPrice, sameEnt, samePrice } = itemToPriceAndEnt({
|
||||
item,
|
||||
orgId,
|
||||
internalProductId: "test",
|
||||
feature,
|
||||
isCustom: false,
|
||||
features,
|
||||
});
|
||||
|
||||
const ent = newEnt || sameEnt;
|
||||
const price = newPrice || samePrice;
|
||||
|
||||
if (ent) {
|
||||
entitlements.push(ent);
|
||||
}
|
||||
if (price) {
|
||||
prices.push(price);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert entitlements array to record keyed by feature_id
|
||||
const entitlementsRecord: Record<string, Entitlement> = {};
|
||||
for (const ent of entitlements) {
|
||||
if (ent.feature_id) {
|
||||
entitlementsRecord[ent.feature_id] = ent;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: productV2.id,
|
||||
name: productV2.name,
|
||||
is_default: productV2.is_default,
|
||||
is_add_on: productV2.is_add_on,
|
||||
entitlements: entitlementsRecord,
|
||||
prices,
|
||||
free_trial: productV2.free_trial,
|
||||
group: productV2.group,
|
||||
};
|
||||
};
|
||||
@@ -150,6 +150,7 @@ export const constructArrearItem = ({
|
||||
|
||||
export const constructArrearProratedItem = ({
|
||||
featureId,
|
||||
featureType,
|
||||
pricePerUnit = 10,
|
||||
includedUsage = 1,
|
||||
config = {
|
||||
@@ -160,6 +161,7 @@ export const constructArrearProratedItem = ({
|
||||
rolloverConfig,
|
||||
}: {
|
||||
featureId: string;
|
||||
featureType?: ProductItemFeatureType;
|
||||
pricePerUnit?: number;
|
||||
includedUsage?: number;
|
||||
config?: ProductItemConfig;
|
||||
@@ -178,6 +180,7 @@ export const constructArrearProratedItem = ({
|
||||
...(rolloverConfig ? { rollover: rolloverConfig } : {}),
|
||||
},
|
||||
usage_limit: usageLimit,
|
||||
...(featureType ? { feature_type: featureType } : {}),
|
||||
};
|
||||
|
||||
return item;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
CreateFreeTrialSchema,
|
||||
type CreateReward,
|
||||
FeatureUsageType,
|
||||
type FreeTrial,
|
||||
FreeTrialDuration,
|
||||
type ProductItem,
|
||||
type ProductV2,
|
||||
@@ -143,6 +144,16 @@ export const constructProduct = ({
|
||||
id ||
|
||||
(isAnnual ? `${type}-annual` : interval ? `${type}-${interval}` : type);
|
||||
|
||||
let free_trial: CreateFreeTrial | null = null;
|
||||
if (freeTrial) {
|
||||
free_trial = freeTrial as FreeTrial;
|
||||
} else if (trial) {
|
||||
free_trial = CreateFreeTrialSchema.parse({
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
});
|
||||
}
|
||||
|
||||
const product: ProductV2 = {
|
||||
id: id_,
|
||||
name: id
|
||||
@@ -158,15 +169,7 @@ export const constructProduct = ({
|
||||
is_default: (type === "free" && isDefault) || forcePaidDefault,
|
||||
version: 1,
|
||||
group: group || "",
|
||||
free_trial:
|
||||
freeTrial || trial
|
||||
? (CreateFreeTrialSchema.parse({
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: false,
|
||||
card_required: true,
|
||||
}) as any)
|
||||
: null,
|
||||
free_trial: free_trial as FreeTrial,
|
||||
created_at: Date.now(),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
ApiVersion,
|
||||
customerProducts,
|
||||
customers,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { TestContext } from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
|
||||
export const createSharedProducts = async ({
|
||||
products,
|
||||
ctx,
|
||||
}: {
|
||||
products: ProductV2[];
|
||||
ctx: TestContext;
|
||||
}) => {
|
||||
const { db } = ctx;
|
||||
|
||||
let cusProducts = await ctx.db.query.customerProducts.findMany({
|
||||
where: inArray(
|
||||
customerProducts.product_id,
|
||||
products.map((p) => p.id),
|
||||
),
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
});
|
||||
cusProducts = cusProducts.filter((cp) => cp.product.org_id === ctx.org.id);
|
||||
|
||||
if (cusProducts.length > 10) {
|
||||
throw new Error("Too many customers under shared default free product");
|
||||
}
|
||||
|
||||
await ctx.db.delete(customers).where(
|
||||
and(
|
||||
inArray(
|
||||
customers.internal_id,
|
||||
cusProducts.map((cp) => cp.internal_customer_id),
|
||||
),
|
||||
eq(customers.env, ctx.env),
|
||||
eq(customers.org_id, ctx.org.id),
|
||||
),
|
||||
);
|
||||
|
||||
const autumn = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
try {
|
||||
await createProducts({
|
||||
db,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
autumn,
|
||||
products,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[createSharedProducts] Failed to create shared products:",
|
||||
error,
|
||||
);
|
||||
console.error(
|
||||
"Product IDs:",
|
||||
products.map((p) => p.id),
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -12,18 +12,25 @@ export const initCustomerV3 = async ({
|
||||
customerData,
|
||||
attachPm,
|
||||
withTestClock = true,
|
||||
withDefault = false,
|
||||
defaultProductId,
|
||||
}: {
|
||||
ctx: TestContext;
|
||||
customerId: string;
|
||||
attachPm?: "success" | "fail";
|
||||
customerData?: CustomerData;
|
||||
withTestClock?: boolean;
|
||||
withDefault?: boolean;
|
||||
defaultProductId?: string;
|
||||
}) => {
|
||||
const name = customerId;
|
||||
const email = `${customerId}@example.com`;
|
||||
const fingerprint_ = "";
|
||||
const { stripeCli } = ctx;
|
||||
const autumn = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
const autumn = new AutumnInt({
|
||||
version: ApiVersion.V1_2,
|
||||
secretKey: ctx.orgSecretKey,
|
||||
});
|
||||
|
||||
let testClockId: string | undefined;
|
||||
|
||||
@@ -53,6 +60,8 @@ export const initCustomerV3 = async ({
|
||||
// @ts-expect-error
|
||||
fingerprint: customerData?.fingerprint || fingerprint_,
|
||||
stripe_id: stripeCus.id,
|
||||
disable_default: !withDefault,
|
||||
default_product_id: defaultProductId,
|
||||
});
|
||||
|
||||
// 3. Attach payment method
|
||||
|
||||
@@ -8,21 +8,47 @@ export const initProductsV0 = async ({
|
||||
ctx,
|
||||
products,
|
||||
prefix,
|
||||
skipPrefixIds = [],
|
||||
customerId,
|
||||
customerIds,
|
||||
}: {
|
||||
ctx: TestContext;
|
||||
products: ProductV2[];
|
||||
prefix?: string;
|
||||
skipPrefixIds?: string[];
|
||||
customerId?: string;
|
||||
customerIds?: string[];
|
||||
}) => {
|
||||
// 1. Add prefix to products
|
||||
// 1. Add prefix to products (except those in skipPrefixIds)
|
||||
if (prefix) {
|
||||
const productsToPrefix = products.filter(
|
||||
(p) => !skipPrefixIds.includes(p.id),
|
||||
);
|
||||
addPrefixToProducts({
|
||||
products,
|
||||
products: productsToPrefix,
|
||||
prefix,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Create
|
||||
const autumn = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
// 2. Create products using the org's secret key
|
||||
const autumn = new AutumnInt({
|
||||
version: ApiVersion.V1_2,
|
||||
secretKey: ctx.orgSecretKey,
|
||||
});
|
||||
|
||||
if (customerIds) {
|
||||
for (const id of customerIds) {
|
||||
try {
|
||||
await autumn.customers.delete(id);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
if (customerId) {
|
||||
try {
|
||||
await autumn.customers.delete(customerId);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
await createProducts({
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
|
||||
354
server/tests/MIGRATION_GUIDE.md
Normal file
354
server/tests/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,354 @@
|
||||
# Test Migration Guide
|
||||
|
||||
## Overview
|
||||
This guide explains how to migrate test files from the global state pattern to the isolated test context pattern.
|
||||
|
||||
## Quick Start Migration Prompt (Copy & Paste)
|
||||
|
||||
Use this prompt for AI coding agents to migrate test files:
|
||||
|
||||
```
|
||||
Migrate test file [FILE_PATH] from global state to isolated test context.
|
||||
|
||||
**Setup:**
|
||||
1. Create backup: `cp [FILE_PATH] [FILE_PATH.backup.test.ts]` (DO NOT delete backup)
|
||||
2. Read migration guide: @server/tests/MIGRATION_GUIDE.md
|
||||
3. Read original file to understand all test logic
|
||||
|
||||
**Critical Rules:**
|
||||
- PRESERVE ALL test logic, assertions, and edge cases
|
||||
- DO NOT remove force_checkout tests or any existing tests
|
||||
- Replace `compareMainProduct` with `expectCustomerV0Correct`
|
||||
- Use TestFeature enum (Messages, Dashboard, Admin) instead of global features
|
||||
- Products MUST be created with `initProductsV0` BEFORE customer creation
|
||||
- Free trials use this exact structure:
|
||||
```typescript
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day, // Import from @autumn/shared
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
}
|
||||
```
|
||||
|
||||
**Migration Steps:**
|
||||
1. Replace global imports with TestFeature enum
|
||||
2. Define products inline using `constructProduct()` and feature item constructors
|
||||
3. Set `const customerId = testCase;` at top of describe block
|
||||
4. Create AutumnInt instance with `ctx.orgSecretKey` and `ApiVersion.V1_2`
|
||||
5. In beforeAll:
|
||||
- Call `initProductsV0({ ctx, products, prefix: testCase, customerId })` FIRST
|
||||
- Then call `initCustomerV3({ ctx, customerId, ... })`
|
||||
6. Replace `compareMainProduct` with `expectCustomerV0Correct`
|
||||
7. For entitlement types, use: `ApiCustomerV1["entitlements"][number]`
|
||||
8. When checking entitlements, iterate through REFERENCE product (what you sent), not customer data
|
||||
|
||||
**Testing:**
|
||||
Run: `bun test --timeout 0 [FILE_PATH]`
|
||||
|
||||
If you encounter unfamiliar utility functions, STOP and ASK how to handle them.
|
||||
```
|
||||
|
||||
## Detailed Migration Prompt for Coding Agent
|
||||
|
||||
```
|
||||
Migrate the test file [FILE_PATH] from using global state to isolated test context.
|
||||
|
||||
Reference the migration guide at @server/tests/MIGRATION_GUIDE.md for the full pattern.
|
||||
|
||||
**Critical Requirements:**
|
||||
1. DO NOT remove any existing test logic - preserve ALL test cases and assertions
|
||||
2. DO NOT remove any force_checkout tests or other edge case tests
|
||||
3. Compare line-by-line with the original file to ensure nothing is lost
|
||||
4. If you encounter unfamiliar utility functions (beyond `compareMainProduct`), STOP and ASK the user how to handle them - do not attempt to migrate them on your own
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Create a Backup Copy**
|
||||
- BEFORE making any changes, create a copy of the test file for reference
|
||||
- Example: `cp basic2.test.ts basic2.test.ts.backup`
|
||||
- This allows you to compare line-by-line during migration to ensure nothing is lost
|
||||
- Delete the backup file after migration is complete and verified
|
||||
|
||||
2. **Replace Global Imports**
|
||||
- Remove: `import { features, products } from "tests/global.js";`
|
||||
- Add: `import { TestFeature } from "tests/setup/v2Features.js";`
|
||||
|
||||
3. **Create Inline Product Definitions**
|
||||
- Use `constructProduct()` to define products directly in the test file
|
||||
- Use `constructFeatureItem()`, `constructPrepaidItem()`, etc. for items
|
||||
- Reference TestFeature enum instead of global features object
|
||||
- Add a unique prefix to product IDs (e.g., testCase name)
|
||||
|
||||
4. **Update Test Setup**
|
||||
- Add `const customerId = testCase;` at the top of describe block
|
||||
- Create AutumnInt instance with org secret key:
|
||||
```typescript
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
```
|
||||
- In beforeAll:
|
||||
- Call `initProductsV0({ ctx, products: [...], prefix: testCase, customerId })` BEFORE customer creation
|
||||
- **IMPORTANT:** Passing `customerId` to `initProductsV0` automatically handles customer cleanup if they exist - you DO NOT need to manually delete the customer first
|
||||
- Call `initCustomerV3({ ctx, customerId, ... })` AFTER products
|
||||
|
||||
5. **Update Test Assertions**
|
||||
- Replace `compareMainProduct` with `expectCustomerV0Correct` for v0.1 API
|
||||
- Replace references to `features.metered1` with `TestFeature.Messages`
|
||||
- Replace references to `features.boolean1` with `TestFeature.Dashboard`
|
||||
- Use `AutumnCli.getCustomer()` for v0.1 API format (returns `features` object)
|
||||
- Use `autumnV1.customers.get()` for v1.2 API format (returns `entitlements` array)
|
||||
|
||||
**Type Helpers:**
|
||||
- For entitlement types from v0.1 API, use: `ApiCustomerV1["entitlements"][number]`
|
||||
- Import from: `import type { ApiCustomerV1 } from "@shared/api/customers/previousVersions/apiCustomerV1.js";`
|
||||
- Example:
|
||||
```typescript
|
||||
const addOnBalance = cusRes.entitlements.find(
|
||||
(e: ApiCustomerV1["entitlements"][number]) =>
|
||||
e.feature_id === TestFeature.Messages && e.interval === "lifetime"
|
||||
);
|
||||
```
|
||||
|
||||
**⚠️ CRITICAL - Entitlement Checking Pattern:**
|
||||
When testing entitlements with `/check` endpoint, you MUST iterate through the **reference product's entitlements** (what you SENT), NOT the customer's entitlements (what they have).
|
||||
|
||||
**WRONG (iterating through customer's entitlements):**
|
||||
```typescript
|
||||
const customer = await AutumnCli.getCustomer(customerId);
|
||||
const entitlements = customer.features; // ❌ WRONG!
|
||||
|
||||
for (const featureId of Object.keys(entitlements)) {
|
||||
const res = await AutumnCli.entitled(customerId, featureId);
|
||||
// checking against customer data...
|
||||
}
|
||||
```
|
||||
|
||||
**CORRECT (iterating through reference product's entitlements):**
|
||||
```typescript
|
||||
import { convertProductV2ToV1 } from "@/internal/products/productUtils/productV2Utils/convertProductV2ToV1.js";
|
||||
|
||||
// Convert ProductV2 to V1 to get reference entitlements
|
||||
const proProdV1 = convertProductV2ToV1({
|
||||
productV2: proProd,
|
||||
orgId: ctx.org.id,
|
||||
features: ctx.features,
|
||||
});
|
||||
const proEntitlements = proProdV1.entitlements;
|
||||
|
||||
// Iterate through reference product's entitlements
|
||||
for (const entitlement of Object.values(proEntitlements)) {
|
||||
const res = await AutumnCli.entitled(customerId, entitlement.feature_id);
|
||||
// Check that the response matches what we SENT...
|
||||
expect(res.allowed).toBe(true);
|
||||
if (entitlement.allowance) {
|
||||
const balance = res.balances.find(b => b.feature_id === entitlement.feature_id);
|
||||
expect(balance?.balance).toBe(entitlement.allowance);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This pattern ensures you're testing what you SENT against what you GET back from the API.
|
||||
|
||||
**⚠️ IMPORTANT - Unfamiliar Utility Functions:**
|
||||
If you encounter utility functions that you're not sure how to migrate (e.g., `compareMainProduct`, `expectProductCorrect`, `checkEntitlements`, or other custom assertion helpers):
|
||||
- **DO NOT attempt to migrate or replace them on your own**
|
||||
- **STOP and ASK the user**: "I found utility function [FUNCTION_NAME] at line [LINE]. How should I handle this in the migration?"
|
||||
- Wait for explicit instructions on the correct replacement function or pattern
|
||||
- Common replacements so far:
|
||||
- `compareMainProduct` → `expectCustomerV0Correct`
|
||||
- But there may be others that need different handling!
|
||||
|
||||
6. **Verify All Logic Preserved**
|
||||
- Check that every test case from the original file exists
|
||||
- Check that every assertion is present
|
||||
- Check that force_checkout tests are included
|
||||
- Check that edge case tests are not removed
|
||||
|
||||
7. **Update Test Case ID**
|
||||
- Change `testCase = "testname"` to keep original name (not "testname-new")
|
||||
- Update console.log messages to use correct testCase
|
||||
|
||||
8. **Run Tests**
|
||||
- Verify all tests pass with `bun test --timeout 0 [FILE_PATH]`
|
||||
- The `--timeout 0` flag disables test timeouts, which is necessary for tests that involve checkout flows and longer async operations
|
||||
|
||||
**Example Migration:**
|
||||
|
||||
Before:
|
||||
```typescript
|
||||
import { features, products } from "tests/global.js";
|
||||
|
||||
const testCase = "basic1";
|
||||
describe("basic1", () => {
|
||||
const customerId = testCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({ ctx, customerId });
|
||||
});
|
||||
|
||||
test("should have correct entitlements", async () => {
|
||||
const entitled = await AutumnCli.entitled(customerId, features.metered1.id);
|
||||
expect(entitled.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
After:
|
||||
```typescript
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const freeProd = constructProduct({
|
||||
type: "free",
|
||||
isDefault: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 5,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "basic1";
|
||||
const customerId = testCase;
|
||||
|
||||
describe("basic1", () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Passing customerId automatically handles cleanup
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [freeProd],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct entitlements", async () => {
|
||||
const entitled = await AutumnCli.entitled(customerId, TestFeature.Messages);
|
||||
expect(entitled.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**After Migration:**
|
||||
- Replace the original file (not create a .new.test.ts file)
|
||||
- Verify tests pass
|
||||
- Report any issues or edge cases found
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Product Construction
|
||||
```typescript
|
||||
// Free product with feature
|
||||
const freeProd = constructProduct({
|
||||
type: "free",
|
||||
isDefault: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 5,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Pro product (matches global products.pro)
|
||||
// - Boolean feature (Dashboard)
|
||||
// - Metered feature (Messages) with 10 allowance
|
||||
// - Unlimited feature (Admin)
|
||||
// - Monthly subscription price ($20)
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Pro product with free trial
|
||||
// IMPORTANT: Free trial structure must use this exact format
|
||||
const proWithTrial = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day, // Import FreeTrialDuration from @autumn/shared
|
||||
unique_fingerprint: true, // Set to true to prevent duplicate trials per fingerprint
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Add-on product
|
||||
const addOnProd = constructProduct({
|
||||
type: "paid",
|
||||
id: "addon",
|
||||
isAddOn: true,
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 500, // $5.00
|
||||
billingUnits: 100,
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Feature Mapping
|
||||
- `features.metered1` → `TestFeature.Messages`
|
||||
- `features.boolean1` → `TestFeature.Dashboard`
|
||||
- `features.metered2` → Create new feature if needed
|
||||
|
||||
### API Version Differences
|
||||
- **v0.1 API** (AutumnCli): Returns `{ features: { [featureId]: {...} } }`
|
||||
- **v1.2 API** (AutumnInt): Returns `{ entitlements: [...] }`
|
||||
|
||||
## Why This Migration?
|
||||
|
||||
1. **Parallel Test Isolation**: Tests can run in parallel without conflicting
|
||||
2. **No Global State**: Each test has its own products and data
|
||||
3. **Test Independence**: Tests don't depend on setup order
|
||||
4. **Better Debugging**: Each test is self-contained and easier to understand
|
||||
457
server/tests/MIGRATION_TRACKER.md
Normal file
457
server/tests/MIGRATION_TRACKER.md
Normal file
@@ -0,0 +1,457 @@
|
||||
# Test Migration Tracker
|
||||
|
||||
Track the progress of migrating test files from global state to isolated test context.
|
||||
|
||||
## Migration Status
|
||||
|
||||
Legend:
|
||||
- ✅ = Migrated and passing
|
||||
- 🚧 = In progress
|
||||
- ⏳ = Not started
|
||||
- ⚠️ = Needs review
|
||||
- ❌ = Skipped/Archived
|
||||
|
||||
## Test Files to Migrate
|
||||
|
||||
### Basic Tests
|
||||
- [x] ✅ `tests/attach/basic/basic1.test.ts` - Migrated
|
||||
- [x] ✅ `tests/attach/basic/basic2.test.ts` - Migrated (renamed from basic4)
|
||||
- [x] ✅ `tests/attach/basic/basic3.test.ts` - Migrated (renamed from basic5)
|
||||
- [x] ✅ `tests/attach/basic/basic6.test.ts` - Migrated
|
||||
- [x] ✅ `tests/attach/basic/basic7.test.ts` - Migrated
|
||||
- [x] ✅ `tests/attach/basic/basic8.test.ts` - Migrated
|
||||
- [x] ✅ `tests/attach/basic/basic9.test.ts` - Migrated
|
||||
- [x] ✅ `tests/attach/basic/basic10.test.ts` - Migrated
|
||||
|
||||
### Downgrade Tests
|
||||
- [x] ✅ `tests/attach/downgrade/downgrade5.test.ts` - Migrated (global→isolated with shared products)
|
||||
- [x] ✅ `tests/attach/downgrade/downgrade6.test.ts` - Migrated (global→isolated with shared products)
|
||||
- [x] ✅ `tests/attach/downgrade/downgrade7.test.ts` - Migrated (global→isolated with shared products)
|
||||
|
||||
### Multi-Product Tests
|
||||
- [ ] ⏳ `tests/attach/multiProduct/multiProduct1.ts`
|
||||
- [ ] ⏳ `tests/attach/multiProduct/multiProduct2.ts`
|
||||
|
||||
### Other Tests
|
||||
- [ ] ⏳ `tests/attach/others/others4.ts`
|
||||
- [ ] ⏳ `tests/attach/others/others5.ts`
|
||||
|
||||
### Upgrade (Old) Tests
|
||||
- [ ] ⏳ `tests/attach/upgradeOld/upgradeOld1.ts`
|
||||
- [ ] ⏳ `tests/attach/upgradeOld/upgradeOld2.ts`
|
||||
- [ ] ⏳ `tests/attach/upgradeOld/upgradeOld3.ts`
|
||||
- [ ] ⏳ `tests/attach/upgradeOld/upgradeOld4.ts`
|
||||
|
||||
### Core Tests
|
||||
- [ ] ⏳ `tests/core/cancel/cancel5.test.ts`
|
||||
|
||||
### Continuous Use Tests
|
||||
- [ ] ⏳ `tests/contUse/track/track5.ts`
|
||||
|
||||
### Advanced Tests
|
||||
- [ ] ⏳ `tests/advanced/coupons/coupon1.ts`
|
||||
- [ ] ⏳ `tests/advanced/multiFeature/multiFeature1.ts`
|
||||
- [ ] ⏳ `tests/advanced/multiFeature/multiFeature2.ts`
|
||||
- [ ] ⏳ `tests/advanced/multiFeature/multiFeature3.ts`
|
||||
|
||||
### Archived Tests (Review if needed)
|
||||
- [ ] ❌ `tests/archives/arrear_prorated/arrear_prorated2.ts`
|
||||
- [ ] ❌ `tests/archives/arrear_prorated/arrear_prorated3.ts`
|
||||
- [ ] ❌ `tests/archives/coupon1 copy.ts`
|
||||
|
||||
## Utility Files (Don't Migrate)
|
||||
These are helper files, not tests:
|
||||
- `tests/utils/compare.ts`
|
||||
- `tests/utils/advancedUsageUtils.ts`
|
||||
|
||||
## Migration Prompt
|
||||
|
||||
When ready to migrate a file, use this prompt:
|
||||
|
||||
```
|
||||
Migrate the test file [FILE_PATH] from using global state to isolated test context.
|
||||
|
||||
Reference the migration guide at @server/tests/MIGRATION_GUIDE.md for the full pattern.
|
||||
|
||||
**Critical Requirements:**
|
||||
1. DO NOT remove any existing test logic - preserve ALL test cases and assertions
|
||||
2. DO NOT remove any force_checkout tests or other edge case tests
|
||||
3. Compare line-by-line with the original file to ensure nothing is lost
|
||||
4. Replace the original file (not create a .new.test.ts file)
|
||||
5. Update testCase ID to match original (e.g., "basic2" not "basic2-new")
|
||||
|
||||
After migration, run: `bun test [FILE_PATH]` to verify all tests pass.
|
||||
```
|
||||
|
||||
## Recent Progress (2025-10-24)
|
||||
|
||||
### Migration Tests
|
||||
- [x] ✅ `tests/attach/migrations/migration1.test.ts` - Mocha→Bun migration
|
||||
- [x] ✅ `tests/attach/migrations/migration2.test.ts` - Mocha→Bun migration
|
||||
- [x] ✅ `tests/attach/migrations/migration3.test.ts` - Mocha→Bun migration
|
||||
- [x] ✅ `tests/attach/migrations/migration4.test.ts` - Mocha→Bun migration
|
||||
- [x] ✅ `tests/attach/migrations/runMigrationTest.ts` - Chai→Bun assertions
|
||||
|
||||
### Shared Products Created
|
||||
- [x] ✅ `tests/attach/downgrade/sharedProducts.ts` - Created shared products for downgrade tests
|
||||
|
||||
## Final Status (2025-10-24)
|
||||
|
||||
### G1.sh Test Suite Status
|
||||
**All 48 test files verified using Bun test framework:**
|
||||
- ✅ tests/check/basic (10 files)
|
||||
- ✅ tests/attach/basic (6 files)
|
||||
- ✅ tests/attach/upgrade (7 files)
|
||||
- ✅ tests/attach/downgrade (7 files)
|
||||
- ✅ tests/attach/free (2 files)
|
||||
- ✅ tests/attach/addOn (2 files)
|
||||
- ✅ tests/attach/entities (5 files)
|
||||
- ✅ tests/attach/checkout (8 files)
|
||||
|
||||
### G2.sh Test Suite Status
|
||||
**All 35 active test files migrated to Bun:**
|
||||
- ✅ Migrations (5 files)
|
||||
- ✅ NewVersion (3 files)
|
||||
- ✅ UpgradeOld (5 files including sharedProducts)
|
||||
- ✅ Others (8 files, 1 deleted)
|
||||
- ✅ UpdateEnts (5 files including utility)
|
||||
- ✅ Prepaid (5 files, 2 commented out)
|
||||
- ✅ Advanced/check (1 file)
|
||||
- ✅ Interval/upgrade (3 files)
|
||||
- ✅ Interval/multiSub (3 files)
|
||||
- ✅ Interval utility (1 file)
|
||||
|
||||
## Progress Summary
|
||||
- **Total Test Files in g1+g2**: 83
|
||||
- **Migrated**: 83 (100%)
|
||||
- **In Progress**: 0 (0%)
|
||||
- **Remaining**: 0 (0%)
|
||||
|
||||
## ✅ G2.sh Migration Complete! (All 35 files migrated)
|
||||
|
||||
### Migration Tests (5 files)
|
||||
- [x] ✅ `tests/attach/migrations/migration1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/migrations/migration2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/migrations/migration3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/migrations/migration4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/migrations/runMigrationTest.ts` - Utility (Chai→Bun)
|
||||
|
||||
### NewVersion Tests (3 files)
|
||||
- [x] ✅ `tests/attach/newVersion/newVersion1.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/newVersion/newVersion2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/newVersion/newVersion3.test.ts` - Already migrated
|
||||
|
||||
### UpgradeOld Tests (5 files)
|
||||
- [x] ✅ `tests/attach/upgradeOld/upgradeOld1.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/upgradeOld/upgradeOld2.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/upgradeOld/upgradeOld3.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/upgradeOld/upgradeOld4.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/upgradeOld/sharedProducts.ts` - Created for global→isolated migration
|
||||
|
||||
### Others Tests (9 files)
|
||||
- [x] ✅ `tests/attach/others/others1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others4.ts` - Deleted (was commented out)
|
||||
- [x] ✅ `tests/attach/others/others5.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others6.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others7.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others8.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/others/others9.test.ts` - Mocha→Bun
|
||||
|
||||
### UpdateEnts Tests (5 files)
|
||||
- [x] ✅ `tests/attach/updateEnts/updateEnts1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/updateEnts/updateEnts2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/updateEnts/updateEnts3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/updateEnts/updateEnts4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/updateEnts/expectUpdateEnts.ts` - Utility (Chai→Bun)
|
||||
|
||||
### Prepaid Tests (7 files)
|
||||
- [x] ✅ `tests/attach/prepaid/prepaid1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/prepaid/prepaid2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/prepaid/prepaid3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/prepaid/prepaid4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/attach/prepaid/prepaid5.test.ts` - Mocha→Bun
|
||||
- [x] 🔕 `tests/attach/prepaid/prepaid6.ts` - Commented out (not migrated)
|
||||
- [x] 🔕 `tests/attach/prepaid/prepaid7.ts` - Commented out (not migrated)
|
||||
|
||||
### Advanced Tests (1 file)
|
||||
- [x] ✅ `tests/advanced/check/check1.test.ts` - Mocha→Bun
|
||||
|
||||
### Interval Tests (7 files)
|
||||
- [x] ✅ `tests/interval/upgrade/interval1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/upgrade/interval2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/upgrade/interval3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/multiSub/multiSubInterval1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/multiSub/multiSubInterval2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/multiSub/multiSubInterval3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/interval/intervalUtils1.test.ts` - Mocha→Bun
|
||||
|
||||
## G3 Migration Complete! (All 19 files)
|
||||
|
||||
### contUse/entities (5 files)
|
||||
- [x] ✅ `tests/contUse/entities/entity1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/entities/entity2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/entities/entity3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/entities/entity4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/entities/entity5.test.ts` - Mocha→Bun
|
||||
|
||||
### contUse/update (5 files)
|
||||
- [x] ✅ `tests/contUse/update/updateContUse1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/update/updateContUse2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/update/updateContUse3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/update/updateContUse4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/update/updateContUse5.test.ts` - Mocha→Bun
|
||||
|
||||
### contUse/track (6 files)
|
||||
- [x] ✅ `tests/contUse/track/track1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/track/track2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/track/track3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/track/track4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/track/track5.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/track/track6.test.ts` - Mocha→Bun
|
||||
|
||||
### contUse/roles (3 files)
|
||||
- [x] ✅ `tests/contUse/roles/role1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/roles/role2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/contUse/roles/role3.test.ts` - Mocha→Bun
|
||||
|
||||
## G4 Migration Complete! (All 47 files)
|
||||
|
||||
### merged/downgrade (8 files)
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade4.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade5.test.ts` - Already migrated
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade6.test.ts` - Already migrated
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade8.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/downgrade/mergedDowngrade9.test.ts` - Mocha→Bun
|
||||
|
||||
### merged/prepaid (3 files)
|
||||
- [x] ✅ `tests/merged/prepaid/mergedPrepaid1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/prepaid/mergedPrepaid2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/merged/prepaid/mergedPrepaid3.test.ts` - Mocha→Bun
|
||||
|
||||
### Other merged/core directories (36 files - all already migrated)
|
||||
- [x] ✅ merged/group (2 files)
|
||||
- [x] ✅ merged/add (3 files)
|
||||
- [x] ✅ merged/separate (2 files)
|
||||
- [x] ✅ merged/upgrade (4 files)
|
||||
- [x] ✅ merged/trial (8 files)
|
||||
- [x] ✅ merged/addOn (6 files)
|
||||
- [x] ✅ core/cancel (8 files)
|
||||
- [x] ✅ core/multiAttach (6 files + subdirectories)
|
||||
- [x] ✅ core/reset (1 file)
|
||||
|
||||
### Utility Files Updated:
|
||||
- [x] ✅ `tests/merged/mergeUtils/expectSubCorrect.ts` - Chai→Bun assertions (kept as .ts)
|
||||
|
||||
## G5 Migration Complete! (34 files migrated, but only 19 in g5.sh script)
|
||||
|
||||
### multiProduct (2 files + sharedProducts)
|
||||
- [x] ✅ `tests/attach/multiProduct/multiProduct1.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/multiProduct/multiProduct2.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/attach/multiProduct/sharedProducts.ts` - Created
|
||||
|
||||
### usage (4 files + sharedProducts)
|
||||
- [x] ✅ `tests/advanced/usage/usage1.test.ts` - Mocha→Bun + global→isolated
|
||||
- [x] ✅ `tests/advanced/usage/usage2.test.ts` - Mocha→Bun (GPU products still use global)
|
||||
- [x] ✅ `tests/advanced/usage/usage3.test.ts` - Mocha→Bun (GPU products still use global)
|
||||
- [x] ✅ `tests/advanced/usage/usage4.test.ts` - Mocha→Bun (GPU products still use global)
|
||||
- [x] ✅ `tests/advanced/usage/sharedProducts.ts` - Created
|
||||
|
||||
### coupons (3 files)
|
||||
- [x] ✅ `tests/advanced/coupons/coupon1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/coupons/coupon2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/coupons/coupon3.test.ts` - Mocha→Bun
|
||||
|
||||
### referrals (4 files)
|
||||
- [x] ✅ `tests/advanced/referrals/referrals1.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/referrals/referrals2.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/referrals/referrals3.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/referrals/referrals4.test.ts` - Mocha→Bun
|
||||
|
||||
### referrals/paid (4 files)
|
||||
- [x] ✅ `tests/advanced/referrals/paid/referrals13.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/referrals/paid/referrals14.test.ts` - Mocha→Bun
|
||||
- [x] ✅ `tests/advanced/referrals/paid/referrals15.test.ts` - Mocha→Bun
|
||||
- [x] 🔕 `tests/advanced/referrals/paid/referrals16.test.ts` - Commented out
|
||||
|
||||
### updateQuantity (1 file)
|
||||
- [x] ✅ `tests/attach/updateQuantity/updateQuantity1.test.ts` - Mocha→Bun
|
||||
|
||||
### rollovers (6 files) ⚠️ NOT IN g5.sh SCRIPT
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover1.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover2.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover3.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover4.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover5.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/rollovers/rollover6.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
|
||||
### customInterval (5 files) ⚠️ NOT IN g5.sh SCRIPT
|
||||
- [x] ✅ `tests/advanced/customInterval/customInterval1.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/customInterval/customInterval2.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/customInterval/customInterval3.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/customInterval/customInterval4.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/customInterval/customInterval5.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] 🔕 `tests/advanced/customInterval/customInterval6.ts` - Empty file (skipped)
|
||||
|
||||
### usageLimit (4 files) ⚠️ NOT IN g5.sh SCRIPT
|
||||
- [x] ✅ `tests/advanced/usageLimit/usageLimit1.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/usageLimit/usageLimit2.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/usageLimit/usageLimit3.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
- [x] ✅ `tests/advanced/usageLimit/usageLimit4.test.ts` - Mocha→Bun (migrated but not in g5.sh)
|
||||
|
||||
### G5 Not Migrated (not in g5.sh script):
|
||||
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature1.ts` (uses old ProductV1 structure)
|
||||
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature2.ts` (uses old ProductV1 structure)
|
||||
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature3.ts` (uses old ProductV1 structure)
|
||||
|
||||
**⚠️ ACTION REQUIRED:** The g5.sh comment says "rollovers, customInterval, usageLimit still use Mocha (not migrated yet)" but these 15 files ARE migrated. Either:
|
||||
1. Add these directories to g5.sh script, OR
|
||||
2. Create a new test group (g7.sh) for these migrated advanced tests
|
||||
|
||||
## G6 - Alex Tests (⏳ NOT MIGRATED - Still Using Mocha)
|
||||
|
||||
### Alex Integration Tests (6 test files)
|
||||
- [ ] ⏳ `tests/alex/01_free.ts` - Uses Mocha (not migrated)
|
||||
- [ ] ⏳ `tests/alex/02_pro.ts` - Uses Mocha (not migrated)
|
||||
- [ ] ⏳ `tests/alex/03_premium.ts` - Uses Mocha (not migrated)
|
||||
- [ ] ⏳ `tests/alex/04_topups.ts` - Uses Mocha (not migrated)
|
||||
- [ ] ⏳ `tests/alex/05_cancel.ts` - Uses Mocha (not migrated)
|
||||
- [ ] ⏳ `tests/alex/06_switch.ts` - Uses Mocha (not migrated)
|
||||
|
||||
### Utility Files (3 files)
|
||||
- `tests/alex/00_setup.ts` - Setup file (ignored in g6.sh)
|
||||
- `tests/alex/utils.ts` - Helper utilities
|
||||
- `tests/alex/init.ts` - Initialization utilities
|
||||
|
||||
**Note:** g6.sh runs these tests using `npx mocha --parallel` with comment "will be migrated later"
|
||||
|
||||
## Final Migration Summary
|
||||
|
||||
### Totals:
|
||||
- **G1:** 47 files ✅
|
||||
- **G2:** 35 files ✅ (includes 7 interval tests)
|
||||
- **G3:** 19 files ✅
|
||||
- **G4:** 65 files ✅ (all merged/core tests)
|
||||
- **G5:** 19 files in script ✅ + 15 files migrated but not in script ⚠️
|
||||
- **G6:** 6 files ⏳ (NOT migrated - still using Mocha)
|
||||
- **Total Migrated to Bun:** 219 files (204 in scripts + 15 orphaned)
|
||||
- **Total in Test Scripts (g1-g5):** 185 files
|
||||
- **Not migrated:**
|
||||
- 3 files (multiFeature 1-3 - ProductV1 structure) ⏸️
|
||||
- 6 files (alex tests - still using Mocha) ⏳
|
||||
- 15 files (rollovers, customInterval, usageLimit - migrated but not in g5.sh) ⚠️
|
||||
|
||||
### Helper Functions Created/Updated:
|
||||
1. ✅ `checkUsageInvoiceAmountV2` - V2 wrapper for usage invoice validation
|
||||
2. ✅ `expectSubCorrect.ts` - Updated Chai→Bun assertions
|
||||
|
||||
### Shared Products Files Created:
|
||||
1. ✅ `tests/attach/basic/sharedProducts.ts` (pre-existing)
|
||||
2. ✅ `tests/attach/downgrade/sharedProducts.ts`
|
||||
3. ✅ `tests/attach/upgradeOld/sharedProducts.ts`
|
||||
4. ✅ `tests/attach/multiProduct/sharedProducts.ts`
|
||||
5. ✅ `tests/advanced/usage/sharedProducts.ts`
|
||||
|
||||
### Shell Scripts Status:
|
||||
- ✅ `scripts/testGroups/g1.sh` - Uses `BUN_PARALLEL_COMPACT` (47 files)
|
||||
- ✅ `scripts/testGroups/g2.sh` - Uses `BUN_PARALLEL_COMPACT` (35 files, includes interval tests)
|
||||
- ✅ `scripts/testGroups/g3.sh` - Uses `BUN_PARALLEL_COMPACT` (19 files)
|
||||
- ✅ `scripts/testGroups/g4.sh` - Uses `BUN_PARALLEL_COMPACT` (65 files)
|
||||
- ⚠️ `scripts/testGroups/g5.sh` - Uses `BUN_PARALLEL_COMPACT` (19 files)
|
||||
- **MISSING:** rollovers (6), customInterval (5), usageLimit (4) directories
|
||||
- Comment says these "still use Mocha" but they ARE migrated
|
||||
- ⏳ `scripts/testGroups/g6.sh` - Uses `npx mocha --parallel` (6 files, not migrated)
|
||||
|
||||
### All before() → beforeAll() Replaced:
|
||||
- ✅ Verified: 0 test files still using `before()` (all occurrences replaced with `beforeAll()`)
|
||||
- ✅ All test files now use proper Bun test syntax
|
||||
|
||||
### Cleanup Actions Completed:
|
||||
- ✅ Deleted 15 Mocha duplicate .ts files where .test.ts versions existed (coupons, referrals, usage)
|
||||
- ✅ Renamed 1 Bun duplicate to .backup.ts (updateQuantity1.ts)
|
||||
- ✅ Created backups for all newly migrated files
|
||||
|
||||
### Migration Status:
|
||||
- ✅ All ProductV1→ProductV2 conversions complete (except 3 multiFeature files + 6 alex files)
|
||||
- ✅ All Mocha→Bun framework migrations complete (except 3 multiFeature files + 6 alex files)
|
||||
- ✅ All global state → isolated migrations complete for migrated files
|
||||
- ✅ All tests preserve original logic and assertions
|
||||
- ✅ Test groups G1-G4 ready for parallel Bun execution
|
||||
- ⚠️ G5 is partial - missing 15 migrated test files (rollovers, customInterval, usageLimit)
|
||||
- ⏳ G6 (alex tests) still uses Mocha framework
|
||||
|
||||
---
|
||||
|
||||
## 🚨 CRITICAL DISCREPANCIES FOUND
|
||||
|
||||
### Issue 1: G2 Missing Interval Tests in Tracker
|
||||
**Status:** FIXED ✅
|
||||
- Added 7 interval test files to tracker (interval/upgrade, interval/multiSub)
|
||||
- Updated G2 count from 28 to 35 files
|
||||
|
||||
### Issue 2: G5 - Orphaned Migrated Tests
|
||||
**Status:** ⚠️ NEEDS ACTION
|
||||
- **15 test files are migrated but NOT in g5.sh script:**
|
||||
- `tests/advanced/rollovers/` (6 files)
|
||||
- `tests/advanced/customInterval/` (5 files)
|
||||
- `tests/advanced/usageLimit/` (4 files)
|
||||
- **g5.sh comment is outdated:** Says these "still use Mocha (not migrated yet)" but they ARE migrated
|
||||
- **Action needed:** Either add these to g5.sh OR create g7.sh for them
|
||||
|
||||
### Issue 3: G6 Not Tracked
|
||||
**Status:** FIXED ✅
|
||||
- Added G6 section tracking 6 alex test files (still using Mocha)
|
||||
- These are integration tests that will need migration later
|
||||
|
||||
### Issue 4: Incorrect Total Counts
|
||||
**Status:** FIXED ✅
|
||||
- Old claim: "204 files migrated"
|
||||
- **Actual:** 219 files migrated to Bun (but only 185 are in test scripts g1-g5)
|
||||
- 15 orphaned files exist but aren't run by any script
|
||||
|
||||
---
|
||||
|
||||
## 📋 RECOMMENDED ACTIONS
|
||||
|
||||
1. **Update g5.sh to include orphaned tests:**
|
||||
```bash
|
||||
# Add to scripts/testGroups/g5.sh:
|
||||
BUN_PARALLEL_COMPACT \
|
||||
'server/tests/advanced/coupons' \
|
||||
'server/tests/attach/updateQuantity' \
|
||||
'server/tests/advanced/referrals' \
|
||||
'server/tests/advanced/referrals/paid' \
|
||||
'server/tests/attach/multiProduct' \
|
||||
'server/tests/advanced/usage' \
|
||||
'server/tests/advanced/rollovers' \
|
||||
'server/tests/advanced/customInterval' \
|
||||
'server/tests/advanced/usageLimit' \
|
||||
--max=6
|
||||
```
|
||||
|
||||
2. **Update g5.sh comment:**
|
||||
- Remove: "Note: advanced/multiFeature, advanced/rollovers, advanced/customInterval, advanced/usageLimit still use Mocha (not migrated yet)"
|
||||
- Replace: "Note: advanced/multiFeature still uses Mocha (not migrated yet)"
|
||||
|
||||
3. **Consider migrating G6 (alex tests):**
|
||||
- 6 integration test files still using Mocha
|
||||
- Would complete the Mocha→Bun migration (except multiFeature)
|
||||
|
||||
---
|
||||
|
||||
## ✅ VERIFIED COUNTS (Post-Sweep)
|
||||
|
||||
- **G1:** 47 files ✅ (matches script)
|
||||
- **G2:** 35 files ✅ (matches script - corrected from 28)
|
||||
- **G3:** 19 files ✅ (matches script)
|
||||
- **G4:** 65 files ✅ (matches script)
|
||||
- **G5:** 19 files in script, 15 files orphaned ⚠️
|
||||
- **G6:** 6 files using Mocha ⏳
|
||||
- **Total in scripts (g1-g5):** 185 files
|
||||
- **Total migrated to Bun:** 219 files
|
||||
- **Orphaned (migrated but not in scripts):** 15 files
|
||||
- **Still using Mocha:** 9 files (3 multiFeature + 6 alex)
|
||||
31
server/tests/TEST_GUIDE.md
Normal file
31
server/tests/TEST_GUIDE.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Test Writing Guide
|
||||
|
||||
## Initial Notes (To be organized later)
|
||||
|
||||
### Customer Initialization
|
||||
|
||||
#### Default Products
|
||||
- For tests involving default products, use the `withDefault: true` flag in `initCustomerV3()`
|
||||
- This ensures the customer is created with the default product attached
|
||||
- Example:
|
||||
```typescript
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: false,
|
||||
withDefault: true, // Attach default product on creation
|
||||
});
|
||||
```
|
||||
|
||||
#### Fingerprint
|
||||
- For tests involving fingerprint, pass in `fingerprint` through `customerData` in `initCustomerV3()`
|
||||
- Example:
|
||||
```typescript
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" }, // Pass fingerprint here
|
||||
withTestClock: false,
|
||||
});
|
||||
```
|
||||
229
server/tests/advanced/coupons/coupon1.test.ts
Normal file
229
server/tests/advanced/coupons/coupon1.test.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { rewards } from "tests/global.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import {
|
||||
advanceTestClock,
|
||||
completeCheckoutForm,
|
||||
getDiscount,
|
||||
} from "tests/utils/stripeUtils.js";
|
||||
import {
|
||||
addPrefixToProducts,
|
||||
getBasePrice,
|
||||
} from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
|
||||
const testCase = "coupon1";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
});
|
||||
|
||||
const simulateOneCycle = async ({
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
stripeCli,
|
||||
autumn,
|
||||
testClockId,
|
||||
couponAmount,
|
||||
curUnix,
|
||||
}: {
|
||||
customerId: string;
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
stripeCli: Stripe;
|
||||
autumn: AutumnInt;
|
||||
testClockId: string;
|
||||
couponAmount: number;
|
||||
curUnix: number;
|
||||
}) => {
|
||||
const usage = Math.random() * 100000 + 10000;
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
// Expected invoice total
|
||||
const expectedTotal = await getExpectedInvoiceTotal({
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
stripeCli,
|
||||
});
|
||||
|
||||
couponAmount -= expectedTotal;
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices![0].total).toBe(0);
|
||||
|
||||
const cusDiscount = await getDiscount({
|
||||
stripeCli: stripeCli,
|
||||
stripeId: customer.stripe_id!,
|
||||
});
|
||||
|
||||
expect(cusDiscount).toBeDefined();
|
||||
|
||||
expect(getOriginalCouponId(cusDiscount.coupon?.id)).toBe(
|
||||
rewards.rolloverAll.id,
|
||||
);
|
||||
|
||||
expect(cusDiscount.coupon?.amount_off).toBe(
|
||||
Math.round(couponAmount * 100),
|
||||
);
|
||||
|
||||
return {
|
||||
couponAmount,
|
||||
curUnix,
|
||||
};
|
||||
};
|
||||
|
||||
describe(
|
||||
chalk.yellow(
|
||||
`${testCase} - Testing invoice credits reward, apply to all product`,
|
||||
),
|
||||
() => {
|
||||
const customerId = "coupon1";
|
||||
let stripeCli: Stripe;
|
||||
let customer: Customer;
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let couponAmount = rewards.rolloverAll.discount_config.discount_value;
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
products: [pro],
|
||||
orgId: org.id,
|
||||
env,
|
||||
db,
|
||||
autumn,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
// CYCLE 0
|
||||
test("should attach pro", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
await completeCheckoutForm(
|
||||
res.checkout_url,
|
||||
undefined,
|
||||
rewards.rolloverAll.id,
|
||||
);
|
||||
|
||||
await timeout(10000);
|
||||
|
||||
couponAmount -= getBasePrice({ product: pro });
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({ customer, product: pro });
|
||||
|
||||
expect(customer.invoices![0].total).toBe(0);
|
||||
|
||||
const cusDiscount = await getDiscount({
|
||||
stripeCli,
|
||||
stripeId: customer.stripe_id!,
|
||||
});
|
||||
|
||||
expect(cusDiscount).toBeDefined();
|
||||
expect(getOriginalCouponId(cusDiscount.coupon?.id)).toBe(
|
||||
rewards.rolloverAll.id,
|
||||
);
|
||||
expect(cusDiscount.coupon?.amount_off).toBe(couponAmount * 100);
|
||||
});
|
||||
|
||||
test("should run one cycle and have correct invoice + coupon amount", async () => {
|
||||
const res = await simulateOneCycle({
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
stripeCli,
|
||||
autumn,
|
||||
testClockId,
|
||||
couponAmount,
|
||||
curUnix: new Date().getTime(),
|
||||
});
|
||||
|
||||
couponAmount = res.couponAmount;
|
||||
curUnix = res.curUnix;
|
||||
});
|
||||
|
||||
// CYCLE 1
|
||||
test("should run another cycle and have correct invoice + coupon amount", async () => {
|
||||
const res = await simulateOneCycle({
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
stripeCli,
|
||||
autumn,
|
||||
testClockId,
|
||||
couponAmount,
|
||||
curUnix,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
192
server/tests/advanced/coupons/coupon2.test.ts
Normal file
192
server/tests/advanced/coupons/coupon2.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CouponDurationType,
|
||||
type CreateReward,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
RewardType,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import { Decimal } from "decimal.js";
|
||||
import type Stripe from "stripe";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { createProducts, createReward } from "tests/utils/productUtils.js";
|
||||
import { completeCheckoutForm, getDiscount } from "tests/utils/stripeUtils.js";
|
||||
import {
|
||||
addPrefixToProducts,
|
||||
getBasePrice,
|
||||
} from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
});
|
||||
|
||||
const testCase = "coupon2";
|
||||
|
||||
// Create reward input
|
||||
const reward: CreateReward = {
|
||||
id: "usage",
|
||||
name: "usage",
|
||||
promo_codes: [{ code: "usage" }],
|
||||
type: RewardType.InvoiceCredits,
|
||||
discount_config: {
|
||||
discount_value: 10000,
|
||||
duration_type: CouponDurationType.Forever,
|
||||
duration_value: 1,
|
||||
should_rollover: true,
|
||||
apply_to_all: false,
|
||||
price_ids: [],
|
||||
},
|
||||
};
|
||||
|
||||
describe(
|
||||
chalk.yellow(`${testCase} - Testing one-off rollover, apply to usage only`),
|
||||
() => {
|
||||
const customerId = testCase;
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
let db: DrizzleCli;
|
||||
|
||||
let couponAmount = reward.discount_config?.discount_value ?? 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
db = ctx.db;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
db: ctx.db,
|
||||
autumn,
|
||||
products: [pro],
|
||||
});
|
||||
|
||||
await createReward({
|
||||
orgId: org.id,
|
||||
env,
|
||||
db,
|
||||
autumn,
|
||||
reward,
|
||||
productId: pro.id,
|
||||
onlyUsage: true,
|
||||
});
|
||||
});
|
||||
|
||||
// CYCLE 0
|
||||
test("should attach pro with promo code", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
await completeCheckoutForm(res.checkout_url, undefined, reward.id);
|
||||
|
||||
await timeout(10000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have fixed price invoice and correct remaining coupon amount", async () => {
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const fixedPrice = getBasePrice({ product: pro });
|
||||
expect(customer.invoices![0].total).toBe(fixedPrice);
|
||||
|
||||
const cusDiscount = await getDiscount({
|
||||
stripeCli,
|
||||
stripeId: customer.stripe_id!,
|
||||
});
|
||||
|
||||
expect(getOriginalCouponId(cusDiscount.coupon?.id)).toBe(reward.id);
|
||||
expect(cusDiscount.coupon?.amount_off).toBe(couponAmount * 100);
|
||||
});
|
||||
|
||||
// CYCLE 1
|
||||
test("should track usage and have correct invoice amount", async () => {
|
||||
const usage = new Decimal(Math.random() * 1250120 + 10000)
|
||||
.toDecimalPlaces(2)
|
||||
.toNumber();
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const usageTotal = await getExpectedInvoiceTotal({
|
||||
org,
|
||||
env,
|
||||
db,
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
stripeCli,
|
||||
onlyIncludeUsage: true,
|
||||
});
|
||||
|
||||
const basePrice = getBasePrice({ product: pro });
|
||||
|
||||
couponAmount = couponAmount - usageTotal;
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices![0].total).toBe(basePrice);
|
||||
|
||||
const cusDiscount = await getDiscount({
|
||||
stripeCli,
|
||||
stripeId: customer.stripe_id!,
|
||||
});
|
||||
|
||||
expect(getOriginalCouponId(cusDiscount.coupon?.id)).toBe(reward.id);
|
||||
|
||||
expect(cusDiscount.coupon?.amount_off).toBe(
|
||||
Math.round(couponAmount * 100),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
171
server/tests/advanced/coupons/coupon3.test.ts
Normal file
171
server/tests/advanced/coupons/coupon3.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CouponDurationType,
|
||||
type CreateReward,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
RewardType,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { expectAttachCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts, createReward } from "tests/utils/productUtils.js";
|
||||
import {
|
||||
addPrefixToProducts,
|
||||
getBasePrice,
|
||||
} from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
});
|
||||
|
||||
const oneOff = constructProduct({
|
||||
type: "one_off",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Create reward input
|
||||
const rewardId = "attach_coupon";
|
||||
const promoCode = "attach_coupon_code";
|
||||
const reward: CreateReward = {
|
||||
id: rewardId,
|
||||
name: "attach_coupon",
|
||||
promo_codes: [{ code: promoCode }],
|
||||
type: RewardType.FixedDiscount,
|
||||
discount_config: {
|
||||
discount_value: 5,
|
||||
duration_type: CouponDurationType.OneOff,
|
||||
duration_value: 1,
|
||||
should_rollover: true,
|
||||
apply_to_all: true,
|
||||
price_ids: [],
|
||||
},
|
||||
};
|
||||
|
||||
const testCase = "coupon3";
|
||||
describe(chalk.yellow(`${testCase} - Testing attach coupon`), () => {
|
||||
const customerId = testCase;
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
let db: DrizzleCli;
|
||||
|
||||
const couponAmount = reward.discount_config!.discount_value;
|
||||
|
||||
beforeAll(async () => {
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
db = ctx.db;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, oneOff],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
db: ctx.db,
|
||||
autumn,
|
||||
products: [pro, oneOff],
|
||||
});
|
||||
|
||||
await createReward({
|
||||
orgId: org.id,
|
||||
env,
|
||||
db,
|
||||
autumn,
|
||||
reward,
|
||||
productId: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
// CYCLE 0
|
||||
test("should attach pro with reward ID", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
reward: rewardId,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectAttachCorrect({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
const invoice = customer.invoices![0];
|
||||
const basePrice = getBasePrice({ product: pro });
|
||||
expect(invoice.total).toBe(basePrice - couponAmount);
|
||||
});
|
||||
|
||||
test("should attach one off with reward ID", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
reward: rewardId,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectAttachCorrect({
|
||||
customer,
|
||||
product: oneOff,
|
||||
});
|
||||
|
||||
const invoice = customer.invoices![0];
|
||||
const basePrice = getBasePrice({ product: oneOff });
|
||||
expect(invoice.total).toBe(basePrice - couponAmount);
|
||||
expect(invoice.product_ids).toContain(oneOff.id);
|
||||
});
|
||||
|
||||
test("should attach one off with promo code", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
reward: promoCode,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectAttachCorrect({
|
||||
customer,
|
||||
product: oneOff,
|
||||
});
|
||||
|
||||
expect(customer.invoices!.length).toBe(3);
|
||||
const basePrice = getBasePrice({ product: oneOff });
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const invoice = customer.invoices![i];
|
||||
expect(invoice.total).toBe(basePrice - couponAmount);
|
||||
expect(invoice.product_ids).toContain(oneOff.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
145
server/tests/advanced/customInterval/customInterval1.backup.ts
Normal file
145
server/tests/advanced/customInterval/customInterval1.backup.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval1";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
// constructArrearItem({ featureId: TestFeature.Words }),
|
||||
// constructArrearProratedItem({
|
||||
// featureId: TestFeature.Users,
|
||||
// pricePerUnit: 30,
|
||||
// }),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
it("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).to.equal(2);
|
||||
|
||||
const nextUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(curUnix), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const invoices = customer2.invoices;
|
||||
expect(invoices.length).to.equal(3);
|
||||
expect(invoices[0].product_ids).to.include(premium.id);
|
||||
expect(invoices[0].total).to.equal(getBasePrice({ product: premium }));
|
||||
|
||||
const wordsFeature = customer2.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).to.equal(2);
|
||||
});
|
||||
});
|
||||
129
server/tests/advanced/customInterval/customInterval1.test.ts
Normal file
129
server/tests/advanced/customInterval/customInterval1.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval1";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
// constructArrearItem({ featureId: TestFeature.Words }),
|
||||
// constructArrearProratedItem({
|
||||
// featureId: TestFeature.Users,
|
||||
// pricePerUnit: 30,
|
||||
// }),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
test("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).toBe(2);
|
||||
|
||||
const nextUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(curUnix), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const invoices = customer2.invoices;
|
||||
expect(invoices.length).toBe(3);
|
||||
expect(invoices[0].product_ids).toContain(premium.id);
|
||||
expect(invoices[0].total).toBe(getBasePrice({ product: premium }));
|
||||
|
||||
const wordsFeature = customer2.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).toBe(2);
|
||||
});
|
||||
});
|
||||
118
server/tests/advanced/customInterval/customInterval2.backup.ts
Normal file
118
server/tests/advanced/customInterval/customInterval2.backup.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval2";
|
||||
|
||||
export const pro = constructRawProduct({
|
||||
id: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 0,
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
it("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 2),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const invoiceAmount = await getExpectedInvoiceTotal({
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).to.equal(2);
|
||||
expect(invoiceAmount).to.equal(customer.invoices[0].total);
|
||||
});
|
||||
});
|
||||
102
server/tests/advanced/customInterval/customInterval2.test.ts
Normal file
102
server/tests/advanced/customInterval/customInterval2.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval2";
|
||||
|
||||
export const pro = constructRawProduct({
|
||||
id: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 0,
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
test("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 2),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const invoiceAmount = await getExpectedInvoiceTotal({
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).toBe(2);
|
||||
expect(invoiceAmount).toBe(customer.invoices[0].total);
|
||||
});
|
||||
});
|
||||
160
server/tests/advanced/customInterval/customInterval3.backup.ts
Normal file
160
server/tests/advanced/customInterval/customInterval3.backup.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addDays, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
const prepaidWordsItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Words,
|
||||
price: 10,
|
||||
billingUnits: 1,
|
||||
includedUsage: 0,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
items: [prepaidWordsItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, addOn],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
it("should upgrade to attached add on and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 20).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
const wordBillingSets = 2;
|
||||
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Words,
|
||||
quantity: wordsBillingUnits,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const proProduct = customer.products.find((p) => p.id === pro.id);
|
||||
const invoices = customer.invoices;
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
|
||||
const proratedPrice = calculateProrationAmount({
|
||||
amount: expectedPrice,
|
||||
periodStart: new Date().getTime(),
|
||||
periodEnd: addMonths(new Date(), 2).getTime(),
|
||||
now: curUnix!,
|
||||
});
|
||||
|
||||
expect(invoices[0].product_ids).to.include(addOn.id);
|
||||
expect(invoices[0].total).to.approximately(proratedPrice, 0.1);
|
||||
|
||||
const expectedAddonEnd = addMonths(new Date(), 2);
|
||||
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
|
||||
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
|
||||
|
||||
expect(addOnProduct?.current_period_end).to.be.approximately(
|
||||
expectedAddonEnd.getTime(),
|
||||
approximate,
|
||||
);
|
||||
});
|
||||
});
|
||||
144
server/tests/advanced/customInterval/customInterval3.test.ts
Normal file
144
server/tests/advanced/customInterval/customInterval3.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addDays, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
const prepaidWordsItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Words,
|
||||
price: 10,
|
||||
billingUnits: 1,
|
||||
includedUsage: 0,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
items: [prepaidWordsItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should upgrade to attached add on and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 20).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
const wordBillingSets = 2;
|
||||
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Words,
|
||||
quantity: wordsBillingUnits,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const proProduct = customer.products.find((p) => p.id === pro.id);
|
||||
const invoices = customer.invoices;
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
|
||||
const proratedPrice = calculateProrationAmount({
|
||||
amount: expectedPrice,
|
||||
periodStart: new Date().getTime(),
|
||||
periodEnd: addMonths(new Date(), 2).getTime(),
|
||||
now: curUnix!,
|
||||
});
|
||||
|
||||
expect(invoices[0].product_ids).toContain(addOn.id);
|
||||
expect(invoices[0].total).toBeCloseTo(proratedPrice, 1);
|
||||
|
||||
const expectedAddonEnd = addMonths(new Date(), 2);
|
||||
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
|
||||
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
|
||||
|
||||
expect(addOnProduct?.current_period_end).toBeCloseTo(
|
||||
expectedAddonEnd.getTime(),
|
||||
-Math.log10(approximate),
|
||||
);
|
||||
});
|
||||
});
|
||||
149
server/tests/advanced/customInterval/customInterval4.backup.ts
Normal file
149
server/tests/advanced/customInterval/customInterval4.backup.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct next cycle at on checkout", async () => {
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const expectedNextCycle = addMonths(new Date(), 2);
|
||||
expect(checkout.next_cycle?.starts_at).to.be.approximately(
|
||||
expectedNextCycle.getTime(),
|
||||
1000 * 60 * 60 * 24,
|
||||
);
|
||||
|
||||
expect(checkout.total).to.equal(0);
|
||||
});
|
||||
|
||||
let preview: any;
|
||||
it("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
it("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).to.equal(2);
|
||||
expect(invoices[0].total).to.equal(getBasePrice({ product: pro }));
|
||||
});
|
||||
});
|
||||
133
server/tests/advanced/customInterval/customInterval4.test.ts
Normal file
133
server/tests/advanced/customInterval/customInterval4.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "customInterval4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct next cycle at on checkout", async () => {
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const expectedNextCycle = addMonths(new Date(), 2);
|
||||
expect(checkout.next_cycle?.starts_at).toBeCloseTo(
|
||||
expectedNextCycle.getTime(),
|
||||
-Math.log10(1000 * 60 * 60 * 24),
|
||||
);
|
||||
|
||||
expect(checkout.total).toBe(0);
|
||||
});
|
||||
|
||||
let preview: any;
|
||||
test("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(2);
|
||||
expect(invoices[0].total).toBe(getBasePrice({ product: pro }));
|
||||
});
|
||||
});
|
||||
161
server/tests/advanced/customInterval/customInterval5.backup.ts
Normal file
161
server/tests/advanced/customInterval/customInterval5.backup.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import type { Customer } from "autumn-js";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval5";
|
||||
|
||||
const includedUsage = 500;
|
||||
const monthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
const biMonthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [monthlyWords, biMonthlyWords],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const getBreakdown = ({
|
||||
customer,
|
||||
intervalCount,
|
||||
}: {
|
||||
customer: Customer;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
return wordsFeature.breakdown?.find(
|
||||
(b: any) => b.interval_count === intervalCount,
|
||||
);
|
||||
};
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).to.equal(null);
|
||||
expect(wordsFeature.breakdown?.length).to.equal(2);
|
||||
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 1 && b.interval === "month",
|
||||
),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 2 && b.interval === "month",
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
const trackVal = 300;
|
||||
it("should have correct breakdown after usage", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
// Should deduct
|
||||
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
|
||||
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
|
||||
|
||||
expect(monthlyBreakdown?.balance).to.equal(includedUsage - trackVal);
|
||||
expect(biMonthlyBreakdown?.balance).to.equal(includedUsage);
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const monthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 1,
|
||||
});
|
||||
const biMonthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
expect(monthlyBreakdown2?.balance).to.equal(0);
|
||||
expect(biMonthlyBreakdown2?.balance).to.equal(includedUsage - 100);
|
||||
});
|
||||
});
|
||||
145
server/tests/advanced/customInterval/customInterval5.test.ts
Normal file
145
server/tests/advanced/customInterval/customInterval5.test.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import type { Customer } from "autumn-js";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "customInterval5";
|
||||
|
||||
const includedUsage = 500;
|
||||
const monthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
const biMonthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [monthlyWords, biMonthlyWords],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const getBreakdown = ({
|
||||
customer,
|
||||
intervalCount,
|
||||
}: {
|
||||
customer: Customer;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
return wordsFeature.breakdown?.find(
|
||||
(b: any) => b.interval_count === intervalCount,
|
||||
);
|
||||
};
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).toBe(null);
|
||||
expect(wordsFeature.breakdown?.length).toBe(2);
|
||||
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 1 && b.interval === "month",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 2 && b.interval === "month",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const trackVal = 300;
|
||||
test("should have correct breakdown after usage", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
// Should deduct
|
||||
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
|
||||
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
|
||||
|
||||
expect(monthlyBreakdown?.balance).toBe(includedUsage - trackVal);
|
||||
expect(biMonthlyBreakdown?.balance).toBe(includedUsage);
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const monthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 1,
|
||||
});
|
||||
const biMonthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
expect(monthlyBreakdown2?.balance).toBe(0);
|
||||
expect(biMonthlyBreakdown2?.balance).toBe(includedUsage - 100);
|
||||
});
|
||||
});
|
||||
@@ -39,7 +39,7 @@ describe(`${chalk.yellowBright(`advanced/${testCase}: ensure default trials are
|
||||
|
||||
const curUnix = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
before(async function () {
|
||||
beforeAll(async function () {
|
||||
await setupBefore(this);
|
||||
await setupDefaultTrialBefore({});
|
||||
const { autumnJs } = this;
|
||||
|
||||
@@ -35,7 +35,7 @@ describe(`${chalk.yellowBright(`advanced/${testCase}: ensure trial transitions i
|
||||
|
||||
const curUnix = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
before(async function () {
|
||||
beforeAll(async function () {
|
||||
await setupBefore(this);
|
||||
await setupDefaultTrialBefore({});
|
||||
const { autumnJs } = this;
|
||||
|
||||
@@ -35,7 +35,7 @@ describe(`${chalk.yellowBright(`advanced/${testCase}: ensure trials cancel with
|
||||
|
||||
const curUnix = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
before(async function () {
|
||||
beforeAll(async function () {
|
||||
await setupBefore(this);
|
||||
await setupDefaultTrialBefore({});
|
||||
const { autumnJs } = this;
|
||||
|
||||
236
server/tests/advanced/referrals/paid/referrals13.test.ts
Normal file
236
server/tests/advanced/referrals/paid/referrals13.test.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CusExpand,
|
||||
CusProductStatus,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { products, referralPrograms } from "../../../global.js";
|
||||
|
||||
export const group = "referrals13";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals13: Testing referrals - referrer on Pro, gets discount on next cycle - coupon-based",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-13";
|
||||
const redeemer = "referral13-r1";
|
||||
const redeemerPM = "success";
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
const testClockIds: string[] = [];
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
let redemption: RewardRedemption;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
autumn.customers.delete(mainCustomerId),
|
||||
autumn.customers.delete(redeemer),
|
||||
RewardRedemptionService._resetCustomerRedemptions({
|
||||
db,
|
||||
internalCustomerId: [mainCustomerId, redeemer],
|
||||
}),
|
||||
]);
|
||||
} catch {}
|
||||
|
||||
// Initialize main customer with Pro product already attached
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockIds.push(res.testClockId);
|
||||
|
||||
// Attach Pro product to main customer first
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: products.pro.id,
|
||||
});
|
||||
|
||||
const redeemerRes = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: redeemerPM as "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockIds.push(redeemerRes.testClockId);
|
||||
});
|
||||
|
||||
test("should advance clock 10 days before redeeming", async () => {
|
||||
// Advance 10 days after Pro is attached
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 10,
|
||||
waitForSeconds: 10,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.paidProductImmediateReferrer.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
});
|
||||
|
||||
test("should create redemption for redeemer and fail if redeemed again", async () => {
|
||||
redemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
// Try redeem for redeemer again
|
||||
try {
|
||||
await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Should not be able to redeem again");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have referrer already on Pro, and redeemer gets free product", async () => {
|
||||
const redemptionResult = await autumn.redemptions.get(redemption.id);
|
||||
expect(redemptionResult.redeemer_applied).toBe(true);
|
||||
|
||||
const mainProds = (await autumn.customers.get(mainCustomerId)).products;
|
||||
const redeemerProds = (await autumn.customers.get(redeemer)).products;
|
||||
|
||||
// Main customer (referrer) should have the pro product (already attached)
|
||||
expect(mainProds.length).toBe(1);
|
||||
expect(mainProds[0].id).toBe(products.pro.id);
|
||||
|
||||
// Redeemer should only have the free product (no pro product given in referrer-only program)
|
||||
expect(redeemerProds.length).toBe(1);
|
||||
expect(redeemerProds[0].id).toBe(products.free.id);
|
||||
|
||||
expectProductV1Attached({
|
||||
customer: await autumn.customers.get(mainCustomerId),
|
||||
product: products.pro,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
|
||||
// Verify redeemer only has free product
|
||||
expectProductV1Attached({
|
||||
customer: await autumn.customers.get(redeemer),
|
||||
product: products.free,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
});
|
||||
|
||||
test("should advance test clock and verify referrer gets discount on next Pro cycle", async () => {
|
||||
// Advance 31 days from current time to trigger next billing cycle
|
||||
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 31,
|
||||
waitForSeconds: 25,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Test that main customer's Pro invoice has discount applied
|
||||
const mainCustomerWithInvoices = await autumn.customers.get(
|
||||
mainCustomerId,
|
||||
{
|
||||
expand: [CusExpand.Invoices, CusExpand.Rewards],
|
||||
},
|
||||
);
|
||||
|
||||
const proInvoice = mainCustomerWithInvoices.invoices.find((x) =>
|
||||
x.product_ids.includes(products.pro.id),
|
||||
);
|
||||
|
||||
const expectedTotal = products.pro.prices[0].config.amount;
|
||||
|
||||
const actualTotal = proInvoice?.total;
|
||||
|
||||
if (proInvoice) {
|
||||
// Should have a discount applied - invoice total should be less than full Pro price ($10)
|
||||
expect(actualTotal!).toBeLessThan(expectedTotal);
|
||||
|
||||
// For referrer-only reward, the discount should make it significantly cheaper or free
|
||||
expect(actualTotal!).toBeLessThanOrEqual(expectedTotal / 2);
|
||||
}
|
||||
|
||||
const dbCustomers = await Promise.all(
|
||||
[mainCustomerId, redeemer].map((x) =>
|
||||
CusService.getFull({
|
||||
db,
|
||||
idOrInternalId: x,
|
||||
orgId: org.id,
|
||||
env,
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Expired,
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const expectedProducts = [
|
||||
[
|
||||
// Main referrer - keeps Pro with discount applied
|
||||
{ name: "Free", status: CusProductStatus.Expired },
|
||||
{ name: "Pro", status: CusProductStatus.Active },
|
||||
],
|
||||
[
|
||||
// Redeemer - only has free product (no reward in referrer-only program)
|
||||
{ name: "Free", status: CusProductStatus.Active },
|
||||
],
|
||||
];
|
||||
|
||||
dbCustomers.forEach((customer, index) => {
|
||||
const expectedProductsForCustomer = expectedProducts[index];
|
||||
expectedProductsForCustomer.forEach((expectedProduct) => {
|
||||
const matchingProduct = customer.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.name === expectedProduct.name &&
|
||||
cp.status === expectedProduct.status,
|
||||
);
|
||||
const unMatchedProduct = customer.customer_products.find(
|
||||
(cp) => cp.product.name === expectedProduct.name,
|
||||
);
|
||||
|
||||
expect(matchingProduct).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
245
server/tests/advanced/referrals/paid/referrals14.test.ts
Normal file
245
server/tests/advanced/referrals/paid/referrals14.test.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CusExpand,
|
||||
CusProductStatus,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { products, referralPrograms } from "../../../global.js";
|
||||
|
||||
export const group = "referrals14";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals14: Testing referrals - referrer on Premium (higher tier), gets pro_amount discount - coupon-based",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-14";
|
||||
const redeemer = "referral14-r1";
|
||||
const redeemerPM = "success";
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
const testClockIds: string[] = [];
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
let redemption: RewardRedemption;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
|
||||
autumn.customers.delete(redeemer, { deleteInStripe: true }),
|
||||
RewardRedemptionService._resetCustomerRedemptions({
|
||||
db,
|
||||
internalCustomerId: [mainCustomerId, redeemer],
|
||||
}),
|
||||
]);
|
||||
} catch {}
|
||||
|
||||
// Initialize main customer with Premium product already attached
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockIds.push(res.testClockId);
|
||||
|
||||
// Attach Premium product to main customer first (higher tier than Pro)
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: products.premium.id,
|
||||
});
|
||||
|
||||
const redeemerRes = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: redeemerPM as "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockIds.push(redeemerRes.testClockId);
|
||||
|
||||
// Advance 10 days after Premium is attached, then redeem the code
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 10,
|
||||
waitForSeconds: 5,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.paidProductImmediateReferrer.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
|
||||
// Get referral code again
|
||||
const referralCode2 = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.paidProductImmediateReferrer.id,
|
||||
});
|
||||
|
||||
expect(referralCode2.code).toBe(referralCode.code);
|
||||
});
|
||||
|
||||
test("should create redemption for redeemer and fail if redeemed again", async () => {
|
||||
redemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
// Try redeem for redeemer again
|
||||
try {
|
||||
await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Should not be able to redeem again");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have referrer already on Premium, and redeemer gets free product", async () => {
|
||||
const redemptionResult = await autumn.redemptions.get(redemption.id);
|
||||
expect(redemptionResult.redeemer_applied).toBe(true);
|
||||
|
||||
const mainCus = await autumn.customers.get(mainCustomerId);
|
||||
const redeemerCus = await autumn.customers.get(redeemer);
|
||||
const mainProds = mainCus.products;
|
||||
const redeemerProds = redeemerCus.products;
|
||||
|
||||
// Main customer (referrer) should have the premium product (already attached)
|
||||
expect(mainProds.length).toBe(1);
|
||||
expect(mainProds[0].id).toBe(products.premium.id);
|
||||
|
||||
// Redeemer should only have the free product (no pro product given in referrer-only program)
|
||||
expect(redeemerProds.length).toBe(1);
|
||||
expect(redeemerProds[0].id).toBe(products.free.id);
|
||||
|
||||
expectProductV1Attached({
|
||||
customer: mainCus,
|
||||
product: products.premium,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
|
||||
// Verify redeemer only has free product
|
||||
expectProductV1Attached({
|
||||
customer: redeemerCus,
|
||||
product: products.free,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
});
|
||||
|
||||
test("should advance test clock and verify referrer gets pro_amount discount on Premium cycle", async () => {
|
||||
// Advance 21 more days (total 31 days from start) to trigger next billing cycle
|
||||
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 31,
|
||||
waitForSeconds: 25,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Test that main customer's Premium invoice has pro_amount discount applied
|
||||
const mainCustomerWithInvoices = await autumn.customers.get(
|
||||
mainCustomerId,
|
||||
{
|
||||
expand: [CusExpand.Invoices],
|
||||
},
|
||||
);
|
||||
|
||||
const premiumInvoice = mainCustomerWithInvoices.invoices.find((x) =>
|
||||
x.product_ids.includes(products.premium.id),
|
||||
);
|
||||
if (premiumInvoice) {
|
||||
// Premium costs $50, Pro costs $10 - so referrer should get $10 discount on Premium
|
||||
// Expected: Premium ($50) - Pro amount ($10) = $40
|
||||
const premiumPrice = products.premium.prices[0].config.amount; // $50
|
||||
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
|
||||
const expectedTotal = premiumPrice - proAmount; // $40
|
||||
|
||||
// The invoice total should be exactly Premium price minus pro_amount
|
||||
expect(premiumInvoice.total).toBe(expectedTotal);
|
||||
|
||||
// Verify that the discount was applied (total is less than full Premium price)
|
||||
expect(premiumInvoice.total).toBeLessThan(premiumPrice);
|
||||
}
|
||||
|
||||
const dbCustomers = await Promise.all(
|
||||
[mainCustomerId, redeemer].map((x) =>
|
||||
CusService.getFull({
|
||||
db,
|
||||
idOrInternalId: x,
|
||||
orgId: org.id,
|
||||
env,
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Expired,
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const expectedProducts = [
|
||||
[
|
||||
// Main referrer - keeps Premium with pro_amount discount applied
|
||||
{ name: "Free", status: CusProductStatus.Expired },
|
||||
{ name: "Premium", status: CusProductStatus.Active },
|
||||
],
|
||||
[
|
||||
// Redeemer - only has free product (no reward in referrer-only program)
|
||||
{ name: "Free", status: CusProductStatus.Active },
|
||||
],
|
||||
];
|
||||
|
||||
dbCustomers.forEach((customer, index) => {
|
||||
const expectedProductsForCustomer = expectedProducts[index];
|
||||
expectedProductsForCustomer.forEach((expectedProduct) => {
|
||||
const matchingProduct = customer.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.name === expectedProduct.name &&
|
||||
cp.status === expectedProduct.status,
|
||||
);
|
||||
const unMatchedProduct = customer.customer_products.find(
|
||||
(cp) => cp.product.name === expectedProduct.name,
|
||||
);
|
||||
|
||||
expect(matchingProduct).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
252
server/tests/advanced/referrals/paid/referrals15.test.ts
Normal file
252
server/tests/advanced/referrals/paid/referrals15.test.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CusExpand,
|
||||
CusProductStatus,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { products, referralPrograms } from "../../../global.js";
|
||||
|
||||
export const group = "referrals15";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals15: Testing referrals - referrer starts with no product, gets pro_amount discount - immediate, both - coupon-based",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-15";
|
||||
const redeemer = "referral15-r1";
|
||||
const redeemerPM = "success";
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
const testClockIds: string[] = [];
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
let redemption: RewardRedemption;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
|
||||
autumn.customers.delete(redeemer, { deleteInStripe: true }),
|
||||
RewardRedemptionService._resetCustomerRedemptions({
|
||||
db,
|
||||
internalCustomerId: [mainCustomerId, redeemer],
|
||||
}),
|
||||
]);
|
||||
} catch {}
|
||||
|
||||
// Initialize main customer with NO paid product (just free tier)
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockIds.push(res.testClockId);
|
||||
|
||||
const redeemerRes = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: redeemerPM as "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockIds.push(redeemerRes.testClockId);
|
||||
});
|
||||
|
||||
test("should advance clock 10 days before redeeming", async () => {
|
||||
// Advance 10 days after setup
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 10,
|
||||
waitForSeconds: 10,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.paidProductImmediateAll.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
|
||||
// Get referral code again
|
||||
const referralCode2 = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.paidProductImmediateAll.id,
|
||||
});
|
||||
|
||||
expect(referralCode2.code).toBe(referralCode.code);
|
||||
});
|
||||
|
||||
test("should create redemption for redeemer and fail if redeemed again", async () => {
|
||||
redemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
// Try redeem for redeemer again
|
||||
try {
|
||||
await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Should not be able to redeem again");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have both referrer and redeemer get pro product", async () => {
|
||||
const redemptionResult = await autumn.redemptions.get(redemption.id);
|
||||
expect(redemptionResult.redeemer_applied).toBe(true);
|
||||
|
||||
const mainCus = await autumn.customers.get(mainCustomerId);
|
||||
const redeemerCus = await autumn.customers.get(redeemer);
|
||||
const mainProds = mainCus.products;
|
||||
const redeemerProds = redeemerCus.products;
|
||||
|
||||
// Main customer (referrer) should now have the pro product
|
||||
expect(mainProds.length).toBe(1);
|
||||
expect(mainProds[0].id).toBe(products.pro.id);
|
||||
|
||||
// Redeemer should also have the pro product (both get reward)
|
||||
expect(redeemerProds.length).toBe(1);
|
||||
expect(redeemerProds[0].id).toBe(products.pro.id);
|
||||
|
||||
expectProductV1Attached({
|
||||
customer: mainCus,
|
||||
product: products.pro,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
|
||||
expectProductV1Attached({
|
||||
customer: redeemerCus,
|
||||
product: products.pro,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
});
|
||||
|
||||
test("should advance test clock and verify both customers get pro_amount discount on Pro cycle", async () => {
|
||||
// Advance 31 days from current time to trigger next billing cycle
|
||||
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
|
||||
await Promise.all(
|
||||
testClockIds.map((x) =>
|
||||
advanceTestClock({
|
||||
testClockId: x,
|
||||
numberOfDays: 31,
|
||||
waitForSeconds: 25,
|
||||
stripeCli,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Test that both customers' Pro invoices have pro_amount discount applied
|
||||
const [mainCustomerWithInvoices, redeemerWithInvoices] = await Promise.all([
|
||||
autumn.customers.get(mainCustomerId, {
|
||||
expand: [CusExpand.Invoices],
|
||||
}),
|
||||
autumn.customers.get(redeemer, {
|
||||
expand: [CusExpand.Invoices],
|
||||
}),
|
||||
]);
|
||||
|
||||
// Check main customer (referrer) invoice
|
||||
const mainProInvoice = mainCustomerWithInvoices.invoices.find((x) =>
|
||||
x.product_ids.includes(products.pro.id),
|
||||
);
|
||||
if (mainProInvoice) {
|
||||
// Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
|
||||
const proPrice = products.pro.prices[0].config.amount; // $10
|
||||
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
|
||||
const expectedTotal = proPrice - proAmount; // $0
|
||||
|
||||
expect(mainProInvoice.total).toBe(expectedTotal);
|
||||
}
|
||||
|
||||
// Check redeemer invoice
|
||||
const redeemerProInvoice = redeemerWithInvoices.invoices.find((x) =>
|
||||
x.product_ids.includes(products.pro.id),
|
||||
);
|
||||
if (redeemerProInvoice) {
|
||||
// Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
|
||||
const proPrice = products.pro.prices[0].config.amount; // $10
|
||||
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
|
||||
const expectedTotal = proPrice - proAmount; // $0
|
||||
|
||||
expect(redeemerProInvoice.total).toBe(expectedTotal);
|
||||
}
|
||||
|
||||
const dbCustomers = await Promise.all(
|
||||
[mainCustomerId, redeemer].map((x) =>
|
||||
CusService.getFull({
|
||||
db,
|
||||
idOrInternalId: x,
|
||||
orgId: org.id,
|
||||
env,
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Expired,
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const expectedProducts = [
|
||||
[
|
||||
// Main referrer - has Pro with pro_amount discount applied
|
||||
{ name: "Free", status: CusProductStatus.Expired },
|
||||
{ name: "Pro", status: CusProductStatus.Active },
|
||||
],
|
||||
[
|
||||
// Redeemer - also has Pro with pro_amount discount applied
|
||||
{ name: "Free", status: CusProductStatus.Expired },
|
||||
{ name: "Pro", status: CusProductStatus.Active },
|
||||
],
|
||||
];
|
||||
|
||||
dbCustomers.forEach((customer, index) => {
|
||||
const expectedProductsForCustomer = expectedProducts[index];
|
||||
expectedProductsForCustomer.forEach((expectedProduct) => {
|
||||
const matchingProduct = customer.customer_products.find(
|
||||
(cp) =>
|
||||
cp.product.name === expectedProduct.name &&
|
||||
cp.status === expectedProduct.status,
|
||||
);
|
||||
const unMatchedProduct = customer.customer_products.find(
|
||||
(cp) => cp.product.name === expectedProduct.name,
|
||||
);
|
||||
|
||||
expect(matchingProduct).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
326
server/tests/advanced/referrals/paid/referrals16.test.ts
Normal file
326
server/tests/advanced/referrals/paid/referrals16.test.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
// NOTE: This test is commented out in the original file (referrals16.ts)
|
||||
// Keeping it commented out in the migrated version as well
|
||||
|
||||
// import {
|
||||
// type AppEnv,
|
||||
// CusExpand,
|
||||
// CusProductStatus,
|
||||
// ErrCode,
|
||||
// type Organization,
|
||||
// type ReferralCode,
|
||||
// type RewardRedemption,
|
||||
// } from "@autumn/shared";
|
||||
// import { beforeAll, describe, expect, test } from "bun:test";
|
||||
// import chalk from "chalk";
|
||||
// import type { Stripe } from "stripe";
|
||||
// import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
// import {
|
||||
// advanceTestClock,
|
||||
// completeCheckoutForm,
|
||||
// } from "tests/utils/stripeUtils.js";
|
||||
// import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
// import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
// import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
// import { CusService } from "@/internal/customers/CusService.js";
|
||||
// import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
// import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
// import { products, referralPrograms, rewards } from "../../../global.js";
|
||||
|
||||
// export const group = "referrals16";
|
||||
|
||||
// describe(`${chalk.yellowBright(
|
||||
// "referrals16: Testing referrals - referrer starts with no product, gets pro_amount discount - checkout, both - coupon-based"
|
||||
// )}`, () => {
|
||||
// const mainCustomerId = "main-referral-16";
|
||||
// const redeemer = "referral16-r1";
|
||||
// const redeemerPM = "success";
|
||||
// const autumn: AutumnInt = new AutumnInt();
|
||||
// let stripeCli: Stripe;
|
||||
// const testClockIds: string[] = [];
|
||||
// let referralCode: ReferralCode;
|
||||
|
||||
// let redemption: RewardRedemption;
|
||||
// let db: DrizzleCli;
|
||||
// let org: Organization;
|
||||
// let env: AppEnv;
|
||||
|
||||
// beforeAll(async () => {
|
||||
// stripeCli = ctx.stripeCli;
|
||||
// db = ctx.db;
|
||||
// org = ctx.org;
|
||||
// env = ctx.env;
|
||||
|
||||
// try {
|
||||
// await Promise.all([
|
||||
// autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
|
||||
// autumn.customers.delete(redeemer, { deleteInStripe: true }),
|
||||
// RewardRedemptionService._resetCustomerRedemptions({
|
||||
// db,
|
||||
// internalCustomerId: [mainCustomerId, redeemer],
|
||||
// }),
|
||||
// ]);
|
||||
// } catch {}
|
||||
|
||||
// // Initialize main customer with NO paid product (just free tier)
|
||||
// const res = await initCustomerV3({
|
||||
// ctx,
|
||||
// customerId: mainCustomerId,
|
||||
// attachPm: "success",
|
||||
// });
|
||||
|
||||
// testClockIds.push(res.testClockId);
|
||||
|
||||
// const redeemerRes = await initCustomerV3({
|
||||
// ctx,
|
||||
// customerId: redeemer,
|
||||
// attachPm: redeemerPM as "success",
|
||||
// withTestClock: true,
|
||||
// });
|
||||
|
||||
// testClockIds.push(redeemerRes.testClockId);
|
||||
// });
|
||||
|
||||
// test("should advance clock 10 days before redeeming", async () => {
|
||||
// // Advance 10 days after setup
|
||||
// await Promise.all(
|
||||
// testClockIds.map((x) =>
|
||||
// advanceTestClock({
|
||||
// testClockId: x,
|
||||
// numberOfDays: 10,
|
||||
// waitForSeconds: 10,
|
||||
// stripeCli,
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
// });
|
||||
|
||||
// test("should create code once", async () => {
|
||||
// referralCode = await autumn.referrals.createCode({
|
||||
// customerId: mainCustomerId,
|
||||
// referralId: referralPrograms.paidProductCheckoutAll.id,
|
||||
// });
|
||||
|
||||
// expect(referralCode.code).toBeDefined();
|
||||
|
||||
// // Get referral code again
|
||||
// const referralCode2 = await autumn.referrals.createCode({
|
||||
// customerId: mainCustomerId,
|
||||
// referralId: referralPrograms.paidProductCheckoutAll.id,
|
||||
// });
|
||||
|
||||
// expect(referralCode2.code).toBe(referralCode.code);
|
||||
// });
|
||||
|
||||
// test("should create redemption for redeemer and fail if redeemed again", async () => {
|
||||
// redemption = await autumn.referrals.redeem({
|
||||
// customerId: redeemer,
|
||||
// code: referralCode.code,
|
||||
// });
|
||||
|
||||
// // Try redeem for redeemer again
|
||||
// try {
|
||||
// await autumn.referrals.redeem({
|
||||
// customerId: redeemer,
|
||||
// code: referralCode.code,
|
||||
// });
|
||||
// throw new Error("Should not be able to redeem again");
|
||||
// } catch (error) {
|
||||
// expect(error).toBeInstanceOf(AutumnError);
|
||||
// expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
// }
|
||||
// });
|
||||
|
||||
// test("should have referrer and redeemer still on free tier (reward not triggered yet)", async () => {
|
||||
// const redemptionResult = await autumn.redemptions.get(redemption.id);
|
||||
// expect(redemptionResult.triggered).toBe(false); // Checkout trigger not fired yet
|
||||
|
||||
// const mainCus = await autumn.customers.get(mainCustomerId);
|
||||
// const redeemerCus = await autumn.customers.get(redeemer);
|
||||
// const mainProds = mainCus.products;
|
||||
// const redeemerProds = redeemerCus.products;
|
||||
|
||||
// // Both customers should still only have free product
|
||||
// expect(mainProds.length).toBe(1);
|
||||
// expect(mainProds[0].id).toBe(products.free.id);
|
||||
|
||||
// expect(redeemerProds.length).toBe(1);
|
||||
// expect(redeemerProds[0].id).toBe(products.free.id);
|
||||
|
||||
// expectProductV1Attached({
|
||||
// customer: mainCus,
|
||||
// product: products.free,
|
||||
// status: CusProductStatus.Active,
|
||||
// });
|
||||
|
||||
// expectProductV1Attached({
|
||||
// customer: redeemerCus,
|
||||
// product: products.free,
|
||||
// status: CusProductStatus.Active,
|
||||
// });
|
||||
// });
|
||||
|
||||
// test("should trigger reward when redeemer checks out with Premium", async () => {
|
||||
// // Redeemer purchases Premium product (triggers checkout reward)
|
||||
// const checkoutRes = await autumn.attach({
|
||||
// customer_id: redeemer,
|
||||
// product_id: products.premium.id,
|
||||
// force_checkout: true,
|
||||
// });
|
||||
|
||||
// await completeCheckoutForm(checkoutRes.checkout_url);
|
||||
|
||||
// // Wait a bit for webhook processing
|
||||
// await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
|
||||
// // Now both customers should have the reward applied
|
||||
// const redemptionResult = await autumn.redemptions.get(redemption.id);
|
||||
|
||||
// expect(redemptionResult.applied).toBe(true);
|
||||
|
||||
// const mainCus = await autumn.customers.get(mainCustomerId);
|
||||
// const redeemerCus = await autumn.customers.get(redeemer);
|
||||
// const mainProds = mainCus.products;
|
||||
// const redeemerProds = redeemerCus.products;
|
||||
|
||||
// // Main customer (referrer) should now have the pro product
|
||||
// expect(mainProds.length).toBe(1);
|
||||
// expect(mainProds[0].id).toBe(products.pro.id);
|
||||
|
||||
// // Redeemer should have both Premium (purchased) and the Pro price discount
|
||||
// expect(redeemerProds.length).toBe(1);
|
||||
// const redeemerStripeDiscounts = await stripeCli.subscriptions.retrieve(
|
||||
// redeemerProds.find((x) => x.id === products.premium.id)
|
||||
// ?.subscription_ids?.[0]!,
|
||||
// {
|
||||
// expand: ["discounts"],
|
||||
// }
|
||||
// );
|
||||
|
||||
// const parsedDiscountID = redeemerStripeDiscounts.discounts.find((x) => {
|
||||
// if (typeof x === "string") {
|
||||
// return x;
|
||||
// } else if (typeof x === "object") {
|
||||
// return x.coupon.id;
|
||||
// } else return null;
|
||||
// })!;
|
||||
|
||||
// expect(redeemerStripeDiscounts.discounts.length).toBe(1);
|
||||
// expect(
|
||||
// typeof parsedDiscountID === "object"
|
||||
// ? parsedDiscountID.coupon.id
|
||||
// : parsedDiscountID
|
||||
// ).toBe(rewards.paidProductWithConfig.id);
|
||||
|
||||
// expect(
|
||||
// redeemerProds.find((x) => x.id === products.premium.id)
|
||||
// ).toBeDefined();
|
||||
|
||||
// expectProductV1Attached({
|
||||
// customer: mainCus,
|
||||
// product: products.pro,
|
||||
// status: CusProductStatus.Active,
|
||||
// });
|
||||
|
||||
// expectProductV1Attached({
|
||||
// customer: redeemerCus,
|
||||
// product: products.premium,
|
||||
// status: CusProductStatus.Active,
|
||||
// });
|
||||
// });
|
||||
|
||||
// test("should advance test clock and verify both customers get pro_amount discount on their cycles", async () => {
|
||||
// // Advance 31 days from current time to trigger next billing cycle
|
||||
// await Promise.all(
|
||||
// testClockIds.map((x) =>
|
||||
// advanceTestClock({
|
||||
// testClockId: x,
|
||||
// numberOfDays: 31,
|
||||
// waitForSeconds: 25,
|
||||
// stripeCli,
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
|
||||
// // Test that both customers' invoices have pro_amount discount applied
|
||||
// const [mainCustomerWithInvoices, redeemerWithInvoices] = await Promise.all([
|
||||
// autumn.customers.get(mainCustomerId, {
|
||||
// expand: [CusExpand.Invoices],
|
||||
// }),
|
||||
// autumn.customers.get(redeemer, {
|
||||
// expand: [CusExpand.Invoices],
|
||||
// }),
|
||||
// ]);
|
||||
|
||||
// // Check main customer (referrer) Pro invoice
|
||||
// const mainProInvoice = mainCustomerWithInvoices.invoices.find((x) =>
|
||||
// x.product_ids.includes(products.pro.id)
|
||||
// );
|
||||
// if (mainProInvoice) {
|
||||
// // Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
|
||||
// const proPrice = products.pro.prices[0].config.amount; // $10
|
||||
// const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
|
||||
// const expectedTotal = proPrice - proAmount; // $0
|
||||
|
||||
// expect(mainProInvoice.total).toBe(expectedTotal);
|
||||
// }
|
||||
|
||||
// // Check redeemer Premium invoice (should have $10 off)
|
||||
// const redeemerPremiumInvoice = redeemerWithInvoices.invoices.find((x) =>
|
||||
// x.product_ids.includes(products.premium.id)
|
||||
// );
|
||||
// if (redeemerPremiumInvoice) {
|
||||
// // Premium costs $50, so with pro_amount discount it should be $40 (Premium - Pro amount = $40)
|
||||
// const premiumPrice = products.premium.prices[0].config.amount; // $50
|
||||
// const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
|
||||
// const expectedTotal = premiumPrice - proAmount; // $40
|
||||
|
||||
// expect(redeemerPremiumInvoice.total).toBe(expectedTotal);
|
||||
// }
|
||||
|
||||
// const dbCustomers = await Promise.all(
|
||||
// [mainCustomerId, redeemer].map((x) =>
|
||||
// CusService.getFull({
|
||||
// db,
|
||||
// idOrInternalId: x,
|
||||
// orgId: org.id,
|
||||
// env,
|
||||
// inStatuses: [
|
||||
// CusProductStatus.Active,
|
||||
// CusProductStatus.PastDue,
|
||||
// CusProductStatus.Expired,
|
||||
// ],
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
|
||||
// const expectedProducts = [
|
||||
// [
|
||||
// // Main referrer - has Pro with pro_amount discount applied
|
||||
// { name: "Free", status: CusProductStatus.Expired },
|
||||
// { name: "Pro", status: CusProductStatus.Active },
|
||||
// ],
|
||||
// [
|
||||
// // Redeemer - has both Premium (purchased) and Pro (reward) with discounts
|
||||
// { name: "Free", status: CusProductStatus.Expired },
|
||||
// { name: "Pro", status: CusProductStatus.Active },
|
||||
// { name: "Premium", status: CusProductStatus.Active },
|
||||
// ],
|
||||
// ];
|
||||
|
||||
// dbCustomers.forEach((customer, index) => {
|
||||
// const expectedProductsForCustomer = expectedProducts[index];
|
||||
// expectedProductsForCustomer.forEach((expectedProduct) => {
|
||||
// const matchingProduct = customer.customer_products.find(
|
||||
// (cp) =>
|
||||
// cp.product.name === expectedProduct.name &&
|
||||
// cp.status === expectedProduct.status
|
||||
// );
|
||||
// const unMatchedProduct = customer.customer_products.find(
|
||||
// (cp) => cp.product.name === expectedProduct.name
|
||||
// );
|
||||
|
||||
// expect(matchingProduct).toBeDefined();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
220
server/tests/advanced/referrals/referrals1.test.ts
Normal file
220
server/tests/advanced/referrals/referrals1.test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addDays } from "date-fns";
|
||||
import type { Stripe } from "stripe";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { products, referralPrograms } from "../../global.js";
|
||||
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals1: Testing referrals (on checkout)",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-1";
|
||||
const alternateCustomerId = "alternate-referral-1";
|
||||
const redeemers = ["referral1-r1", "referral1-r2", "referral1-r3"];
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: any;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: mainCustomerId,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn: new AutumnInt({ secretKey: ctx.orgSecretKey }),
|
||||
products: [pro],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
customerId: mainCustomerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
customerData: { fingerprint: "main-referral-1" },
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
mainCustomer = res.customer;
|
||||
testClockId = res.testClockId;
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const batchCreate = [];
|
||||
for (const redeemer of redeemers) {
|
||||
batchCreate.push(
|
||||
initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: "success",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
batchCreate.push(
|
||||
initCustomerV3({
|
||||
ctx,
|
||||
customerId: alternateCustomerId,
|
||||
customerData: { fingerprint: "main-referral-1" },
|
||||
attachPm: "success",
|
||||
}),
|
||||
);
|
||||
await Promise.all(batchCreate);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.onCheckout.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
|
||||
// Get referral code again
|
||||
const referralCode2 = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.onCheckout.id,
|
||||
});
|
||||
|
||||
expect(referralCode2.code).toBe(referralCode.code);
|
||||
});
|
||||
|
||||
test("should fail if same customer tries to redeem code again", async () => {
|
||||
try {
|
||||
await autumn.referrals.redeem({
|
||||
customerId: mainCustomerId,
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Own customer should not be able to redeem code");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerCannotRedeemOwnCode);
|
||||
}
|
||||
|
||||
try {
|
||||
await autumn.referrals.redeem({
|
||||
customerId: alternateCustomerId,
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error(
|
||||
"Own customer (same fingerprint) should not be able to redeem code",
|
||||
);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerCannotRedeemOwnCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should create redemption for each redeemer and fail if redeemed again", async () => {
|
||||
for (const redeemer of redeemers) {
|
||||
const redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
redemptions.push(redemption);
|
||||
}
|
||||
|
||||
// Try redeem for redeemer1 again
|
||||
try {
|
||||
const redemption1 = await autumn.referrals.redeem({
|
||||
customerId: redeemers[0],
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Should not be able to redeem again");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should be triggered (and applied) when redeemers check out", async () => {
|
||||
for (let i = 0; i < redeemers.length; i++) {
|
||||
const redeemer = redeemers[i];
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: redeemer,
|
||||
product_id: products.pro.id,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Get redemption object
|
||||
const redemption = await autumn.redemptions.get(redemptions[i].id);
|
||||
|
||||
// Check if redemption is triggered
|
||||
const count = i + 1;
|
||||
|
||||
if (count > referralPrograms.onCheckout.max_redemptions) {
|
||||
expect(redemption.triggered).toBe(false);
|
||||
expect(redemption.applied).toBe(false);
|
||||
} else {
|
||||
expect(redemption.triggered).toBe(true);
|
||||
expect(redemption.applied).toBe(i === 0);
|
||||
}
|
||||
|
||||
// Check stripe customer
|
||||
const stripeCus = (await stripeCli.customers.retrieve(
|
||||
mainCustomer.processor?.id,
|
||||
)) as Stripe.Customer;
|
||||
|
||||
expect(stripeCus.discount).not.toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
let curTime = new Date();
|
||||
test("customer should have discount for first purchase", async () => {
|
||||
curTime = addDays(addDays(curTime, 7), 4);
|
||||
await advanceTestClock({
|
||||
testClockId,
|
||||
advanceTo: curTime.getTime(),
|
||||
stripeCli,
|
||||
});
|
||||
|
||||
// 1. Get invoice
|
||||
const { invoices } = await autumn.customers.get(mainCustomerId);
|
||||
expect(invoices.length).toBe(2);
|
||||
expect(invoices[0].total).toBe(0);
|
||||
});
|
||||
});
|
||||
136
server/tests/advanced/referrals/referrals2.test.ts
Normal file
136
server/tests/advanced/referrals/referrals2.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addDays } from "date-fns";
|
||||
import type { Stripe } from "stripe";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { products, referralPrograms } from "../../global.js";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals2: Testing referrals (immediate redemption)",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-2";
|
||||
const redeemers = ["referral2-r1", "referral2-r2", "referral2-r3"];
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: Customer;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
const { testClockId: testClockId1, customer } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
});
|
||||
testClockId = testClockId1;
|
||||
mainCustomer = customer;
|
||||
|
||||
const batchCreate = [];
|
||||
for (const redeemer of redeemers) {
|
||||
batchCreate.push(
|
||||
initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: "success",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(batchCreate);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.immediate.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
});
|
||||
|
||||
test("should create redemption for each redeemer and fail if redeemed again", async () => {
|
||||
for (let i = 0; i < redeemers.length; i++) {
|
||||
const redeemer = redeemers[i];
|
||||
const count = i + 1;
|
||||
try {
|
||||
const redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
redemptions.push(redemption);
|
||||
|
||||
if (count > referralPrograms.immediate.max_redemptions) {
|
||||
expect(redemption.triggered).toBe(false);
|
||||
expect(redemption.applied).toBe(false);
|
||||
} else {
|
||||
throw new Error("Should not be able to redeem again");
|
||||
}
|
||||
} catch (error) {
|
||||
if (count > referralPrograms.immediate.max_redemptions) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.ReferralCodeMaxRedemptionsReached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check stripe customer
|
||||
const legacyStripe = createStripeCli({
|
||||
org: org,
|
||||
env: env,
|
||||
legacyVersion: true,
|
||||
});
|
||||
|
||||
const stripeCus = (await legacyStripe.customers.retrieve(
|
||||
mainCustomer.processor?.id,
|
||||
{
|
||||
expand: ["discount"],
|
||||
},
|
||||
)) as Stripe.Customer;
|
||||
|
||||
expect(stripeCus.discount).not.toBe(null);
|
||||
});
|
||||
|
||||
let curTime = new Date();
|
||||
test("customer should have discount for first purchase", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: products.proWithTrial.id,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
curTime = addDays(addDays(curTime, 7), 4);
|
||||
await advanceTestClock({
|
||||
testClockId,
|
||||
advanceTo: curTime.getTime(),
|
||||
stripeCli,
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
// 1. Get invoice
|
||||
const { invoices } = await autumn.customers.get(mainCustomerId);
|
||||
|
||||
expect(invoices!.length).toBe(2);
|
||||
expect(invoices![0].total).toBe(0);
|
||||
});
|
||||
});
|
||||
130
server/tests/advanced/referrals/referrals3.test.ts
Normal file
130
server/tests/advanced/referrals/referrals3.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
type Customer,
|
||||
ErrCode,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import { compareProductEntitlements } from "tests/utils/compare.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { features, products, referralPrograms } from "../../global.js";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals3: Testing free product referrals",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-3";
|
||||
const redeemers = ["referral3-r1", "referral3-r2", "referral3-r3"];
|
||||
let autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: Customer;
|
||||
|
||||
beforeAll(async () => {
|
||||
autumn = new AutumnInt({ secretKey: ctx.orgSecretKey });
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1, customer } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
customerData: { fingerprint: "main-referral-3" },
|
||||
});
|
||||
testClockId = testClockId1;
|
||||
mainCustomer = customer;
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: products.proWithTrial.id,
|
||||
});
|
||||
|
||||
const batchCreate = [];
|
||||
for (const redeemer of redeemers) {
|
||||
batchCreate.push(
|
||||
initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemer,
|
||||
attachPm: "success",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(batchCreate);
|
||||
});
|
||||
|
||||
test("should create code once", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.freeProduct.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
});
|
||||
|
||||
test("should create redemption for each redeemer and fail if redeemed again", async () => {
|
||||
for (const redeemer of redeemers) {
|
||||
const redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
customerId: redeemer,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
redemptions.push(redemption);
|
||||
}
|
||||
|
||||
// Try redeem for redeemer1 again
|
||||
try {
|
||||
const redemption1 = await autumn.referrals.redeem({
|
||||
customerId: redeemers[0],
|
||||
code: referralCode.code,
|
||||
});
|
||||
throw new Error("Should not be able to redeem again");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AutumnError);
|
||||
expect((error as AutumnError).code).toBe(ErrCode.CustomerAlreadyRedeemedReferralCode);
|
||||
}
|
||||
});
|
||||
|
||||
test("should be triggered (and applied) when redeemers check out", async () => {
|
||||
for (let i = 0; i < redeemers.length; i++) {
|
||||
const redeemer = redeemers[i];
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: redeemer,
|
||||
product_id: products.pro.id,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Get redemption object
|
||||
const redemption = await autumn.redemptions.get(redemptions[i].id);
|
||||
|
||||
// Check if redemption is triggered
|
||||
const count = i + 1;
|
||||
|
||||
if (count > referralPrograms.freeProduct.max_redemptions) {
|
||||
expect(redemption.triggered).toBe(false);
|
||||
expect(redemption.applied).toBe(false);
|
||||
} else {
|
||||
// 1. Check that main customer has free add on
|
||||
compareProductEntitlements({
|
||||
customerId: mainCustomerId,
|
||||
product: products.freeAddOn,
|
||||
features,
|
||||
quantity: count,
|
||||
});
|
||||
|
||||
compareProductEntitlements({
|
||||
customerId: redeemer,
|
||||
product: products.freeAddOn,
|
||||
features,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
117
server/tests/advanced/referrals/referrals4.test.ts
Normal file
117
server/tests/advanced/referrals/referrals4.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { Customer, ReferralCode, RewardRedemption } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addDays, addHours } from "date-fns";
|
||||
import type { Stripe } from "stripe";
|
||||
import { compareProductEntitlements } from "tests/utils/compare.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { features, products, referralPrograms } from "../../global.js";
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals4: Testing free product referrals with trial",
|
||||
)}`, () => {
|
||||
const mainCustomerId = "main-referral-4";
|
||||
const redeemerId = "referral4-r1";
|
||||
|
||||
let autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: Customer;
|
||||
let redeemer: Customer;
|
||||
|
||||
let testClockId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
autumn = new AutumnInt({ secretKey: ctx.orgSecretKey });
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId: mainCustomerId,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: mainCustomerId,
|
||||
product_id: products.proWithTrial.id,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1, customer } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId: redeemerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1;
|
||||
redeemer = customer;
|
||||
});
|
||||
|
||||
test("should create referral code", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.freeProduct.id,
|
||||
});
|
||||
|
||||
expect(referralCode.code).toBeDefined();
|
||||
});
|
||||
|
||||
test("should create redemption for each redeemer and fail if redeemed again", async () => {
|
||||
const redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
customerId: redeemerId,
|
||||
code: referralCode.code,
|
||||
});
|
||||
|
||||
redemptions.push(redemption);
|
||||
});
|
||||
|
||||
test("should not be triggered because of trial", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: redeemerId,
|
||||
product_id: products.proWithTrial.id,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Get redemption object
|
||||
const redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
|
||||
expect(redemption.triggered).toBe(false);
|
||||
});
|
||||
|
||||
test("should be triggered after trial ends", async () => {
|
||||
const advanceTo = addHours(
|
||||
addDays(new Date(), 7),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime();
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo,
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
|
||||
expect(redemption.triggered).toBe(true);
|
||||
|
||||
compareProductEntitlements({
|
||||
customerId: mainCustomerId,
|
||||
product: products.freeAddOn,
|
||||
features,
|
||||
quantity: 1,
|
||||
});
|
||||
|
||||
compareProductEntitlements({
|
||||
customerId: redeemerId,
|
||||
product: products.freeAddOn,
|
||||
features,
|
||||
quantity: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
198
server/tests/advanced/rollovers/rollover1.backup.ts
Normal file
198
server/tests/advanced/rollovers/rollover1.backup.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover1";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
const messageUsage = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messageUsage,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedRollover = Math.min(
|
||||
messagesItem.included_usage - messageUsage,
|
||||
rolloverConfig.max,
|
||||
);
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(expectedRollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
it("should reset again and have correct rollover", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
|
||||
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(100);
|
||||
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
|
||||
expect(msgesFeature?.rollovers[1].balance).to.equal(400);
|
||||
});
|
||||
|
||||
it("should track messages and deduct from rollovers first", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollover1 = msgesFeature?.rollovers[0];
|
||||
// @ts-expect-error
|
||||
const rollover2 = msgesFeature?.rollovers[1];
|
||||
|
||||
expect(rollover1.balance).to.equal(0);
|
||||
expect(rollover2.balance).to.equal(350);
|
||||
});
|
||||
|
||||
it("should track and deduct from rollover + original balance", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers![0].balance).to.equal(0);
|
||||
expect(rollovers![1].balance).to.equal(0);
|
||||
expect(msgesFeature.balance).to.equal(messagesItem.included_usage - 50);
|
||||
});
|
||||
});
|
||||
179
server/tests/advanced/rollovers/rollover1.test.ts
Normal file
179
server/tests/advanced/rollovers/rollover1.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover1";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
const messageUsage = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messageUsage,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedRollover = Math.min(
|
||||
messagesItem.included_usage - messageUsage,
|
||||
rolloverConfig.max,
|
||||
);
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(expectedRollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
test("should reset again and have correct rollover", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
|
||||
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(100);
|
||||
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
|
||||
expect(msgesFeature?.rollovers[1].balance).toBe(400);
|
||||
});
|
||||
|
||||
test("should track messages and deduct from rollovers first", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollover1 = msgesFeature?.rollovers[0];
|
||||
// @ts-expect-error
|
||||
const rollover2 = msgesFeature?.rollovers[1];
|
||||
|
||||
expect(rollover1.balance).toBe(0);
|
||||
expect(rollover2.balance).toBe(350);
|
||||
});
|
||||
|
||||
test("should track and deduct from rollover + original balance", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers![0].balance).toBe(0);
|
||||
expect(rollovers![1].balance).toBe(0);
|
||||
expect(msgesFeature.balance).toBe(messagesItem.included_usage - 50);
|
||||
});
|
||||
});
|
||||
225
server/tests/advanced/rollovers/rollover2.backup.ts
Normal file
225
server/tests/advanced/rollovers/rollover2.backup.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
|
||||
const msgesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
entityFeatureId: TestFeature.Users,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [msgesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover2";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const entities: any[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
const entity1Id = entities[0].id;
|
||||
const entity2Id = entities[1].id;
|
||||
const newEntity1Balance = 300;
|
||||
const newEntity2Balance = 200;
|
||||
const includedUsage = msgesItem.included_usage;
|
||||
const usages = [
|
||||
{
|
||||
entityId: entity1Id,
|
||||
usage: includedUsage - newEntity1Balance,
|
||||
rollover: newEntity1Balance,
|
||||
},
|
||||
{
|
||||
entityId: entity2Id,
|
||||
usage: includedUsage - newEntity2Balance,
|
||||
rollover: newEntity2Balance,
|
||||
},
|
||||
];
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
for (const usage of usages) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: usage.usage,
|
||||
entity_id: usage.entityId,
|
||||
});
|
||||
}
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Run reset cusEnt on ...
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
for (const usage of usages) {
|
||||
const entity = await autumn.entities.get(customerId, usage.entityId);
|
||||
const msgesFeature = entity.features[TestFeature.Messages];
|
||||
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
|
||||
|
||||
expect(msgesFeature.rollovers.length).to.equal(1);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage + expectedRollover);
|
||||
expect(msgesFeature.rollovers[0].balance).to.equal(expectedRollover);
|
||||
}
|
||||
});
|
||||
|
||||
it("should reset again and have correct rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, entity1Id);
|
||||
const entity1Msges = entity1.features[TestFeature.Messages];
|
||||
// 400, 300 -> 400, 100 (max is 500)
|
||||
const rollovers = entity1Msges.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(400);
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, entity2Id);
|
||||
const entity2Msges = entity2.features[TestFeature.Messages];
|
||||
// 400, 200 -> 400, 0 (max is 500)
|
||||
const rollovers2 = entity2Msges.rollovers;
|
||||
expect(rollovers2[0].balance).to.equal(100);
|
||||
expect(rollovers2[1].balance).to.equal(400);
|
||||
});
|
||||
|
||||
it("should track and deduct from oldest rollovers first", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(350);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage + 350);
|
||||
}
|
||||
});
|
||||
|
||||
it("should track past rollovers and deduct from original balance", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
await timeout(2000);
|
||||
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(0);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage - 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
206
server/tests/advanced/rollovers/rollover2.test.ts
Normal file
206
server/tests/advanced/rollovers/rollover2.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
|
||||
const msgesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
entityFeatureId: TestFeature.Users,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [msgesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover2";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const entities: any[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
const entity1Id = entities[0].id;
|
||||
const entity2Id = entities[1].id;
|
||||
const newEntity1Balance = 300;
|
||||
const newEntity2Balance = 200;
|
||||
const includedUsage = msgesItem.included_usage;
|
||||
const usages = [
|
||||
{
|
||||
entityId: entity1Id,
|
||||
usage: includedUsage - newEntity1Balance,
|
||||
rollover: newEntity1Balance,
|
||||
},
|
||||
{
|
||||
entityId: entity2Id,
|
||||
usage: includedUsage - newEntity2Balance,
|
||||
rollover: newEntity2Balance,
|
||||
},
|
||||
];
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
for (const usage of usages) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: usage.usage,
|
||||
entity_id: usage.entityId,
|
||||
});
|
||||
}
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Run reset cusEnt on ...
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
for (const usage of usages) {
|
||||
const entity = await autumn.entities.get(customerId, usage.entityId);
|
||||
const msgesFeature = entity.features[TestFeature.Messages];
|
||||
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
|
||||
|
||||
expect(msgesFeature.rollovers.length).toBe(1);
|
||||
expect(msgesFeature.balance).toBe(includedUsage + expectedRollover);
|
||||
expect(msgesFeature.rollovers[0].balance).toBe(expectedRollover);
|
||||
}
|
||||
});
|
||||
|
||||
test("should reset again and have correct rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, entity1Id);
|
||||
const entity1Msges = entity1.features[TestFeature.Messages];
|
||||
// 400, 300 -> 400, 100 (max is 500)
|
||||
const rollovers = entity1Msges.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(400);
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, entity2Id);
|
||||
const entity2Msges = entity2.features[TestFeature.Messages];
|
||||
// 400, 200 -> 400, 0 (max is 500)
|
||||
const rollovers2 = entity2Msges.rollovers;
|
||||
expect(rollovers2[0].balance).toBe(100);
|
||||
expect(rollovers2[1].balance).toBe(400);
|
||||
});
|
||||
|
||||
test("should track and deduct from oldest rollovers first", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(350);
|
||||
expect(msgesFeature.balance).toBe(includedUsage + 350);
|
||||
}
|
||||
});
|
||||
|
||||
test("should track past rollovers and deduct from original balance", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
await timeout(2000);
|
||||
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(0);
|
||||
expect(msgesFeature.balance).toBe(includedUsage - 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
127
server/tests/advanced/rollovers/rollover3.backup.ts
Normal file
127
server/tests/advanced/rollovers/rollover3.backup.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messagesItem.included_usage - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + rollover;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(rollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
});
|
||||
108
server/tests/advanced/rollovers/rollover3.test.ts
Normal file
108
server/tests/advanced/rollovers/rollover3.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messagesItem.included_usage - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + rollover;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(rollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
});
|
||||
157
server/tests/advanced/rollovers/rollover4.backup.ts
Normal file
157
server/tests/advanced/rollovers/rollover4.backup.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 400,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 300,
|
||||
price: 10,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const paidQuantity = 300;
|
||||
const balance = paidQuantity + messagesItem.included_usage;
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: paidQuantity,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 50;
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: balance - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(balance + rollover);
|
||||
expect(rollovers[0].balance).to.equal(rollover);
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
it("should reset again and have correct rollover", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(balance + newRollover);
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(400);
|
||||
});
|
||||
});
|
||||
138
server/tests/advanced/rollovers/rollover4.test.ts
Normal file
138
server/tests/advanced/rollovers/rollover4.test.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 400,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 300,
|
||||
price: 10,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const paidQuantity = 300;
|
||||
const balance = paidQuantity + messagesItem.included_usage;
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: paidQuantity,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 50;
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: balance - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(balance + rollover);
|
||||
expect(rollovers[0].balance).toBe(rollover);
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
test("should reset again and have correct rollover", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(balance + newRollover);
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(400);
|
||||
});
|
||||
});
|
||||
137
server/tests/advanced/rollovers/rollover5.backup.ts
Normal file
137
server/tests/advanced/rollovers/rollover5.backup.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover5";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free, pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const freeRolloverBalance = freeMsges.included_usage * 2;
|
||||
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(
|
||||
proMsges.included_usage + proRolloverBalance,
|
||||
);
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(500);
|
||||
});
|
||||
});
|
||||
118
server/tests/advanced/rollovers/rollover5.test.ts
Normal file
118
server/tests/advanced/rollovers/rollover5.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover5";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const freeRolloverBalance = freeMsges.included_usage * 2;
|
||||
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(
|
||||
proMsges.included_usage + proRolloverBalance,
|
||||
);
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(500);
|
||||
});
|
||||
});
|
||||
151
server/tests/advanced/rollovers/rollover6.backup.ts
Normal file
151
server/tests/advanced/rollovers/rollover6.backup.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover6";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free, pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const proRolloverBalance = proMsges.included_usage * 2;
|
||||
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(
|
||||
freeMsges.included_usage + freeRolloverBalance,
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(500);
|
||||
});
|
||||
});
|
||||
132
server/tests/advanced/rollovers/rollover6.test.ts
Normal file
132
server/tests/advanced/rollovers/rollover6.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover6";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const proRolloverBalance = proMsges.included_usage * 2;
|
||||
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(
|
||||
freeMsges.included_usage + freeRolloverBalance,
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(500);
|
||||
});
|
||||
});
|
||||
45
server/tests/advanced/usage/sharedProducts.ts
Normal file
45
server/tests/advanced/usage/sharedProducts.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { BillingInterval, ProductItemInterval } from "@autumn/shared";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructArrearItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { createSharedProducts } from "@/utils/scriptUtils/testUtils/createSharedProduct.js";
|
||||
|
||||
/**
|
||||
* Shared products for usage test group
|
||||
* Matches global products.proWithOverage
|
||||
*/
|
||||
|
||||
export const sharedProWithOverage = constructProduct({
|
||||
id: "pro-with-overage",
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000, // $20/month
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Messages,
|
||||
// Overage pricing for usage beyond included
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const initUsageSharedProducts = async () => {
|
||||
await createSharedProducts({
|
||||
ctx,
|
||||
products: [sharedProWithOverage],
|
||||
});
|
||||
};
|
||||
|
||||
// Auto-init on import (backwards compat)
|
||||
await initUsageSharedProducts();
|
||||
139
server/tests/advanced/usage/usage1.test.ts
Normal file
139
server/tests/advanced/usage/usage1.test.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { Customer } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { v1ProductToBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { calculateMetered1Price } from "@/external/stripe/utils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { AutumnCli } from "../../cli/AutumnCli.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { timeout } from "../../utils/genUtils.js";
|
||||
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
|
||||
import { sharedProWithOverage } from "./sharedProducts.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { convertProductV2ToV1 } from "@/internal/products/productUtils/productV2Utils/convertProductV2ToV1.js";
|
||||
|
||||
const testCase = "usage1";
|
||||
|
||||
describe(`${chalk.yellowBright("usage1: Testing basic usage product")}`, () => {
|
||||
const NUM_EVENTS = 50;
|
||||
const customerId = testCase;
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { customer: customer_, testClockId: testClockId_ } =
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: true,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
customer = customer_;
|
||||
testClockId = testClockId_;
|
||||
});
|
||||
|
||||
test("should attach usage based product", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: sharedProWithOverage.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
expectCustomerV0Correct({
|
||||
sent: sharedProWithOverage,
|
||||
cusRes: res,
|
||||
ctx,
|
||||
});
|
||||
});
|
||||
|
||||
test("usage1: should send metered1 events", async () => {
|
||||
const batchUpdates = [];
|
||||
for (let i = 0; i < NUM_EVENTS; i++) {
|
||||
batchUpdates.push(
|
||||
AutumnCli.sendEvent({
|
||||
customerId: customerId,
|
||||
eventName: TestFeature.Messages,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(batchUpdates);
|
||||
await timeout(25000);
|
||||
});
|
||||
|
||||
test("should have correct metered1 balance after sending events", async () => {
|
||||
const res: any = await AutumnCli.entitled(customerId, TestFeature.Messages);
|
||||
|
||||
expect(res!.allowed).toBe(true);
|
||||
|
||||
const balance = res!.balances.find(
|
||||
(balance: any) => balance.feature_id === TestFeature.Messages,
|
||||
);
|
||||
|
||||
// Convert V2 product to V1 to access entitlements
|
||||
const productV1 = convertProductV2ToV1({
|
||||
productV2: sharedProWithOverage,
|
||||
orgId: ctx.org.id,
|
||||
features: ctx.features,
|
||||
});
|
||||
|
||||
const proOverageAmt =
|
||||
productV1.entitlements.messages.allowance;
|
||||
|
||||
expect(res!.allowed, "should be allowed").toBe(true);
|
||||
|
||||
expect(balance?.balance, "should have correct metered1 balance").toBe(
|
||||
proOverageAmt! - NUM_EVENTS,
|
||||
);
|
||||
|
||||
expect(balance?.usage_allowed, "should have usage_allowed").toBe(true);
|
||||
});
|
||||
|
||||
// Check invoice
|
||||
test("should advance stripe test clock and wait for event", async () => {
|
||||
await advanceClockForInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
waitForMeterUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct invoice amount", async () => {
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
const invoices = cusRes!.invoices;
|
||||
|
||||
// Convert V2 product to V1 for price calculations
|
||||
const productV1 = convertProductV2ToV1({
|
||||
productV2: sharedProWithOverage,
|
||||
orgId: ctx.org.id,
|
||||
features: ctx.features,
|
||||
});
|
||||
|
||||
// calculate price
|
||||
const price = calculateMetered1Price({
|
||||
product: productV1,
|
||||
numEvents: NUM_EVENTS,
|
||||
metered1Feature: ctx.features[TestFeature.Messages],
|
||||
});
|
||||
|
||||
expect(invoices.length).toBe(2);
|
||||
|
||||
const invoice = invoices[0];
|
||||
|
||||
const basePrice = v1ProductToBasePrice({
|
||||
prices: productV1.prices,
|
||||
});
|
||||
|
||||
expect(invoice.total, "invoice total should be usage price + base price").toBe(
|
||||
price + basePrice,
|
||||
);
|
||||
});
|
||||
});
|
||||
116
server/tests/advanced/usage/usage2.test.ts
Normal file
116
server/tests/advanced/usage/usage2.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { Decimal } from "decimal.js";
|
||||
import type Stripe from "stripe";
|
||||
import { advanceClockForInvoice } from "tests/utils/stripeUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { AutumnCli } from "../../cli/AutumnCli.js";
|
||||
import { advanceProducts, creditSystems, features } from "../../global.js";
|
||||
import { getCreditsUsed } from "../../utils/advancedUsageUtils.js";
|
||||
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { timeout } from "../../utils/genUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
|
||||
// NOTE: This test uses GPU products from global.ts (advanceProducts.gpuSystemStarter)
|
||||
// These products are not yet converted to ProductV2 format in sharedProducts.ts
|
||||
// The test has been migrated to Bun but still uses ProductV1 from global.ts
|
||||
|
||||
const testCase = "usage2";
|
||||
describe(`${chalk.yellowBright("usage2: Testing basic usage product")}`, () => {
|
||||
const customerId = testCase;
|
||||
const PRECISION = 10;
|
||||
const ASSERT_INVOICE_AMOUNT = true;
|
||||
const CREDIT_MULTIPLIER = 100000;
|
||||
|
||||
let testClockId = "";
|
||||
let totalCreditsUsed = 0;
|
||||
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { testClockId: createdTestClockId } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: true,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = createdTestClockId;
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
});
|
||||
|
||||
test("should attach gpu system starter", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: advanceProducts.gpuSystemStarter.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: advanceProducts.gpuSystemStarter,
|
||||
cusRes: res,
|
||||
ctx,
|
||||
});
|
||||
});
|
||||
|
||||
// Use up events
|
||||
test("should send events and have correct balance (up to 10 DP)", async () => {
|
||||
const eventCount = 20;
|
||||
|
||||
const batchEvents = [];
|
||||
for (let i = 0; i < eventCount; i++) {
|
||||
const randomVal = new Decimal(Math.random().toFixed(PRECISION))
|
||||
.mul(CREDIT_MULTIPLIER)
|
||||
.mul(Math.random() > 0.2 ? 1 : -1)
|
||||
.toNumber();
|
||||
const gpuId = i % 2 === 0 ? features.gpu1.id : features.gpu2.id;
|
||||
|
||||
const creditsUsed = getCreditsUsed(
|
||||
creditSystems.gpuCredits,
|
||||
gpuId,
|
||||
randomVal,
|
||||
);
|
||||
|
||||
totalCreditsUsed = new Decimal(totalCreditsUsed)
|
||||
.plus(creditsUsed)
|
||||
.toNumber();
|
||||
|
||||
batchEvents.push(
|
||||
AutumnCli.sendEvent({
|
||||
customerId: customerId,
|
||||
eventName: gpuId,
|
||||
properties: { value: randomVal },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(batchEvents);
|
||||
|
||||
await timeout(10000);
|
||||
|
||||
const { allowed, balanceObj }: any = await AutumnCli.entitled(
|
||||
customerId,
|
||||
creditSystems.gpuCredits.id,
|
||||
true,
|
||||
);
|
||||
|
||||
const creditAllowance =
|
||||
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
|
||||
|
||||
expect(allowed).toBe(true);
|
||||
expect(balanceObj!.balance).toBe(
|
||||
new Decimal(creditAllowance).minus(totalCreditsUsed).toNumber(),
|
||||
);
|
||||
});
|
||||
|
||||
// Check invoice.created event
|
||||
test("should have correct invoice amount / updated meter balance", async () => {
|
||||
await advanceClockForInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
waitForMeterUpdate: ASSERT_INVOICE_AMOUNT,
|
||||
});
|
||||
});
|
||||
});
|
||||
144
server/tests/advanced/usage/usage3.test.ts
Normal file
144
server/tests/advanced/usage/usage3.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import chalk from "chalk";
|
||||
import { advanceProducts } from "../../global.js";
|
||||
import { AutumnCli } from "../../cli/AutumnCli.js";
|
||||
import { sendGPUEvents } from "../../utils/advancedUsageUtils.js";
|
||||
import { advanceTestClock } from "../../utils/stripeUtils.js";
|
||||
import { expect } from "bun:test";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { checkSubscriptionContainsProducts } from "tests/utils/scheduleCheckUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import Stripe from "stripe";
|
||||
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import { getSubsFromCusId } from "tests/utils/expectUtils/expectSubUtils.js";
|
||||
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
|
||||
// NOTE: This test uses GPU products from global.ts (advanceProducts.gpuSystemStarter, gpuSystemPro)
|
||||
// These products are not yet converted to ProductV2 format in sharedProducts.ts
|
||||
// The test has been migrated to Bun but still uses ProductV1 from global.ts
|
||||
|
||||
const testCase = "usage3";
|
||||
const ASSERT_INVOICE_AMOUNT = true;
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"usage3: upgrade from GPU starter monthly to GPU pro monthly",
|
||||
)}`, () => {
|
||||
const customerId = "usage3";
|
||||
let testClockId = "";
|
||||
let totalCreditsUsed = 0;
|
||||
let stripeCli: Stripe;
|
||||
let curUnix = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
let { testClockId: insertedTestClockId } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: true,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = insertedTestClockId;
|
||||
stripeCli = ctx.stripeCli;
|
||||
});
|
||||
|
||||
// 1. Attach GPU starter monthly
|
||||
test("usage3: should attach GPU starter monthly", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: advanceProducts.gpuSystemStarter.id,
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Send 20 events
|
||||
test("usage3: should send 20 events", async () => {
|
||||
let eventCount = 20;
|
||||
const { creditsUsed } = await sendGPUEvents({
|
||||
customerId,
|
||||
eventCount,
|
||||
});
|
||||
|
||||
totalCreditsUsed = creditsUsed;
|
||||
});
|
||||
|
||||
// 3. Advance test clock by 15 days and upgrade
|
||||
test("should advance test clock by 15 days and upgrade to GPU pro monthly", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
numberOfDays: 15,
|
||||
});
|
||||
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: advanceProducts.gpuSystemPro.id,
|
||||
});
|
||||
|
||||
// MAKE SURE STRIPE SUB ONLY HAS GPU PRO
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: advanceProducts.gpuSystemPro,
|
||||
cusRes: res,
|
||||
ctx,
|
||||
});
|
||||
|
||||
let subscriptionId = res.products[0].subscription_ids![0]!;
|
||||
await checkSubscriptionContainsProducts({
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
subscriptionId,
|
||||
productIds: [advanceProducts.gpuSystemPro.id],
|
||||
});
|
||||
});
|
||||
|
||||
// 4. Check invoice for 15 days of starter usage
|
||||
test("should have invoice for 15 days of starter usage", async () => {
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
const invoices = res!.invoices;
|
||||
|
||||
let basePrice1 = advanceProducts.gpuSystemStarter.prices[0].config.amount;
|
||||
let basePrice2 = advanceProducts.gpuSystemPro.prices[0].config.amount;
|
||||
|
||||
let { subs } = await getSubsFromCusId({
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
stripeCli,
|
||||
productId: advanceProducts.gpuSystemPro.id,
|
||||
});
|
||||
|
||||
let sub = subs[0];
|
||||
|
||||
const { start, end } = subToPeriodStartEnd({ sub });
|
||||
let baseDiff = calculateProrationAmount({
|
||||
periodStart: start * 1000,
|
||||
periodEnd: end * 1000,
|
||||
now: curUnix,
|
||||
amount: basePrice2 - basePrice1,
|
||||
allowNegative: true,
|
||||
});
|
||||
|
||||
let usagePrice = advanceProducts.gpuSystemStarter.prices[1];
|
||||
let overage =
|
||||
totalCreditsUsed -
|
||||
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
|
||||
|
||||
let overagePrice = priceToInvoiceAmount({
|
||||
price: usagePrice,
|
||||
overage,
|
||||
});
|
||||
|
||||
let calculatedTotal = new Decimal(baseDiff)
|
||||
.plus(overagePrice)
|
||||
.toDecimalPlaces(2)
|
||||
.toNumber();
|
||||
|
||||
expect(invoices[0].total).toBe(calculatedTotal);
|
||||
});
|
||||
});
|
||||
112
server/tests/advanced/usage/usage4.test.ts
Normal file
112
server/tests/advanced/usage/usage4.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { Customer } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { AutumnCli } from "../../cli/AutumnCli.js";
|
||||
import { advanceProducts, creditSystems } from "../../global.js";
|
||||
import {
|
||||
checkCreditBalance,
|
||||
checkUsageInvoiceAmount,
|
||||
sendGPUEvents,
|
||||
} from "../../utils/advancedUsageUtils.js";
|
||||
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
|
||||
// NOTE: This test uses GPU products from global.ts (advanceProducts.gpuStarterAnnual)
|
||||
// These products are not yet converted to ProductV2 format in sharedProducts.ts
|
||||
// The test has been migrated to Bun but still uses ProductV1 from global.ts
|
||||
// However, it does use checkUsageInvoiceAmountV2 for the V2 helper function
|
||||
|
||||
const testCase = "usage4";
|
||||
|
||||
describe(`${chalk.yellowBright("usage4: GPU starter annual")}`, () => {
|
||||
const customerId = testCase;
|
||||
let totalCreditsUsed = 0;
|
||||
|
||||
let testClockId = "";
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: true,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId;
|
||||
customer = res.customer;
|
||||
stripeCli = ctx.stripeCli;
|
||||
});
|
||||
|
||||
test("should attach GPU starter annual", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: advanceProducts.gpuStarterAnnual.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: advanceProducts.gpuStarterAnnual,
|
||||
cusRes: res,
|
||||
ctx,
|
||||
});
|
||||
|
||||
expect(res!.invoices.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should send 20 events and have correct balance", async () => {
|
||||
const eventCount = 20;
|
||||
const { creditsUsed } = await sendGPUEvents({
|
||||
customerId,
|
||||
eventCount,
|
||||
});
|
||||
|
||||
totalCreditsUsed = creditsUsed;
|
||||
await checkCreditBalance({
|
||||
customerId,
|
||||
featureId: creditSystems.gpuCredits.id,
|
||||
totalCreditsUsed,
|
||||
originalAllowance:
|
||||
advanceProducts.gpuStarterAnnual.entitlements.gpuCredits.allowance!,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have invoice after a month and correct balance", async () => {
|
||||
await advanceClockForInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
waitForMeterUpdate: true,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
const invoices = res!.invoices;
|
||||
|
||||
const invoiceIndex = invoices.findIndex((invoice: any) =>
|
||||
invoice.product_ids.includes(advanceProducts.gpuStarterAnnual.id),
|
||||
);
|
||||
|
||||
// NOTE: Using checkUsageInvoiceAmount (V1) as gpuStarterAnnual is not yet converted to V2
|
||||
// When GPU products are migrated to ProductV2, this should use checkUsageInvoiceAmountV2
|
||||
await checkUsageInvoiceAmount({
|
||||
invoices,
|
||||
totalUsage: totalCreditsUsed,
|
||||
product: advanceProducts.gpuStarterAnnual,
|
||||
featureId: creditSystems.gpuCredits.id,
|
||||
invoiceIndex,
|
||||
includeBase: false,
|
||||
});
|
||||
|
||||
await checkCreditBalance({
|
||||
customerId,
|
||||
featureId: creditSystems.gpuCredits.id,
|
||||
totalCreditsUsed: 0,
|
||||
originalAllowance:
|
||||
advanceProducts.gpuStarterAnnual.entitlements.gpuCredits.allowance!,
|
||||
});
|
||||
});
|
||||
});
|
||||
151
server/tests/advanced/usageLimit/usageLimit1.backup.ts
Normal file
151
server/tests/advanced/usageLimit/usageLimit1.backup.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 0,
|
||||
usageLimit: 2,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit1";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Entity 3",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Entity 4",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
it("should create more entities than the limit and hit error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should create entities one by one, then hit usage limit", async () => {
|
||||
await autumn.entities.create(customerId, entities[0]);
|
||||
await autumn.entities.create(customerId, entities[1]);
|
||||
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities[2]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct check and get customer value", async () => {
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(check.balance).to.equal(-2);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit).to.equal(userItem.usage_limit);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Users].usage_limit).to.equal(
|
||||
userItem.usage_limit,
|
||||
);
|
||||
});
|
||||
});
|
||||
132
server/tests/advanced/usageLimit/usageLimit1.test.ts
Normal file
132
server/tests/advanced/usageLimit/usageLimit1.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 0,
|
||||
usageLimit: 2,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit1";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Entity 3",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Entity 4",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
test("should create more entities than the limit and hit error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should create entities one by one, then hit usage limit", async () => {
|
||||
await autumn.entities.create(customerId, entities[0]);
|
||||
await autumn.entities.create(customerId, entities[1]);
|
||||
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities[2]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct check and get customer value", async () => {
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(check.balance).toBe(-2);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit).toBe(userItem.usage_limit);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Users].usage_limit).toBe(
|
||||
userItem.usage_limit,
|
||||
);
|
||||
});
|
||||
});
|
||||
195
server/tests/advanced/usageLimit/usageLimit2.backup.ts
Normal file
195
server/tests/advanced/usageLimit/usageLimit2.backup.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const messageItem = constructArrearItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 1,
|
||||
price: 0.5,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const addOnMessages = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
interval: null,
|
||||
includedUsage: 250,
|
||||
}) as LimitedItem;
|
||||
|
||||
const messageAddOn = constructProduct({
|
||||
type: "one_off",
|
||||
items: [addOnMessages],
|
||||
});
|
||||
|
||||
const testCase = "usageLimit2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, messageAddOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, messageAddOn],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const initialUsage =
|
||||
messageItem.included_usage + messageItem.usage_limit! + 1000;
|
||||
|
||||
it("should track more messages than limit and not surpass", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: initialUsage,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(messageItem.usage_limit!);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit!,
|
||||
);
|
||||
});
|
||||
|
||||
it("should purchase add ons and have correct check results", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: messageAddOn.id,
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const expectedBalance =
|
||||
messageItem.included_usage -
|
||||
messageItem.usage_limit! +
|
||||
addOnMessages.included_usage;
|
||||
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(true);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
|
||||
it("should use up all add ons and have correct check results", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: addOnMessages.included_usage + 500,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
});
|
||||
176
server/tests/advanced/usageLimit/usageLimit2.test.ts
Normal file
176
server/tests/advanced/usageLimit/usageLimit2.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructArrearItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 1,
|
||||
price: 0.5,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const addOnMessages = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
interval: null,
|
||||
includedUsage: 250,
|
||||
}) as LimitedItem;
|
||||
|
||||
const messageAddOn = constructProduct({
|
||||
type: "one_off",
|
||||
items: [addOnMessages],
|
||||
});
|
||||
|
||||
const testCase = "usageLimit2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, messageAddOn],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const initialUsage =
|
||||
messageItem.included_usage + messageItem.usage_limit! + 1000;
|
||||
|
||||
test("should track more messages than limit and not surpass", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: initialUsage,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(messageItem.usage_limit!);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit!,
|
||||
);
|
||||
});
|
||||
|
||||
test("should purchase add ons and have correct check results", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: messageAddOn.id,
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const expectedBalance =
|
||||
messageItem.included_usage -
|
||||
messageItem.usage_limit! +
|
||||
addOnMessages.included_usage;
|
||||
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(true);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
|
||||
test("should use up all add ons and have correct check results", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: addOnMessages.included_usage + 500,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
});
|
||||
147
server/tests/advanced/usageLimit/usageLimit3.backup.ts
Normal file
147
server/tests/advanced/usageLimit/usageLimit3.backup.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const messageItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 50,
|
||||
billingUnits: 100,
|
||||
price: 8,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
// const addOnMessages = constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// interval: null,
|
||||
// includedUsage: 250,
|
||||
// }) as LimitedItem;
|
||||
|
||||
// const messageAddOn = constructProduct({
|
||||
// type: "one_off",
|
||||
// items: [addOnMessages],
|
||||
// });
|
||||
|
||||
const testCase = "usageLimit3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
129
server/tests/advanced/usageLimit/usageLimit3.test.ts
Normal file
129
server/tests/advanced/usageLimit/usageLimit3.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 50,
|
||||
billingUnits: 100,
|
||||
price: 8,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
// const addOnMessages = constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// interval: null,
|
||||
// includedUsage: 250,
|
||||
// }) as LimitedItem;
|
||||
|
||||
// const messageAddOn = constructProduct({
|
||||
// type: "one_off",
|
||||
// items: [addOnMessages],
|
||||
// });
|
||||
|
||||
const testCase = "usageLimit3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
112
server/tests/advanced/usageLimit/usageLimit4.backup.ts
Normal file
112
server/tests/advanced/usageLimit/usageLimit4.backup.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const messageItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
includedUsage: 1,
|
||||
pricePerUnit: 10,
|
||||
usageLimit: 3,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for cont use item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
it("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.InvalidInputs,
|
||||
func: async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: messageItem.usage_limit! + 1,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(check.balance).to.equal(0);
|
||||
expect(check.allowed).to.equal(true);
|
||||
});
|
||||
});
|
||||
93
server/tests/advanced/usageLimit/usageLimit4.test.ts
Normal file
93
server/tests/advanced/usageLimit/usageLimit4.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
includedUsage: 1,
|
||||
pricePerUnit: 10,
|
||||
usageLimit: 3,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for cont use item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.InvalidInputs,
|
||||
func: async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: messageItem.usage_limit! + 1,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(check.balance).toBe(0);
|
||||
expect(check.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,9 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import { AutumnCli } from "tests/cli/AutumnCli.js";
|
||||
import { features, products } from "tests/global.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { compareMainProduct } from "tests/utils/compare.js";
|
||||
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
@@ -13,91 +12,103 @@ import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { sharedDefaultFree } from "./sharedProducts.js";
|
||||
|
||||
const freeProd = constructProduct({
|
||||
const free2 = constructProduct({
|
||||
type: "free",
|
||||
id: "free2",
|
||||
isDefault: false,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
// constructFixedPrice({
|
||||
// price: 0,
|
||||
// }),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "basic1";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("basic1: Testing attach free product")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_2 });
|
||||
describe(`${chalk.yellowBright("basic1: Testing attach free, default product")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
console.log(`[basic1] Using org: ${ctx.org.slug} (${ctx.org.id})`);
|
||||
console.log("Customer ID: ", customerId);
|
||||
|
||||
// Create products FIRST so default product can be attached to customer
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free2],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer (will auto-attach default product if exists)
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: false,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [freeProd],
|
||||
prefix: testCase,
|
||||
withDefault: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create customer and have default free active", async () => {
|
||||
const data = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
compareMainProduct({
|
||||
sent: products.free,
|
||||
await expectCustomerV0Correct({
|
||||
sent: sharedDefaultFree,
|
||||
cusRes: data,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct entitlements", async () => {
|
||||
const expectedEntitlement = products.free.entitlements.metered1;
|
||||
|
||||
// Expected: 5 allowance for Messages feature
|
||||
const entitled = (await AutumnCli.entitled(
|
||||
customerId,
|
||||
features.metered1.id,
|
||||
TestFeature.Messages,
|
||||
)) as any;
|
||||
|
||||
console.log("Entitled response:", entitled);
|
||||
const metered1Balance = entitled.balances.find(
|
||||
(balance: any) => balance.feature_id === features.metered1.id,
|
||||
(balance: any) => balance.feature_id === TestFeature.Messages,
|
||||
);
|
||||
|
||||
expect(entitled.allowed).toBe(true);
|
||||
expect(metered1Balance).toBeDefined();
|
||||
expect(metered1Balance.balance).toBe(expectedEntitlement.allowance);
|
||||
expect(metered1Balance.balance).toBe(5);
|
||||
expect(metered1Balance.unlimited).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should have correct boolean1 entitlement", async () => {
|
||||
const entitled = await AutumnCli.entitled(customerId, features.boolean1.id);
|
||||
// Dashboard feature is not included in freeProd, should be false
|
||||
const entitled = await AutumnCli.entitled(
|
||||
customerId,
|
||||
TestFeature.Dashboard,
|
||||
);
|
||||
expect(entitled!.allowed).toBe(false);
|
||||
});
|
||||
|
||||
test("should attach free (with $0 price) and force checkout and succeed", async () => {
|
||||
await autumn.attach({
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: freeProd.id,
|
||||
product_id: free2.id,
|
||||
force_checkout: true,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: freeProd,
|
||||
product: free2,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: freeProd,
|
||||
product: free2,
|
||||
otherProducts: [sharedDefaultFree],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user