chore: solving type errors in test files

This commit is contained in:
John Yeo
2025-11-11 18:04:55 +00:00
436 changed files with 2860 additions and 7311 deletions

View File

@@ -1 +1 @@
1.2.19
1.3.2

View File

@@ -5,6 +5,7 @@
- JS Doc comments should be SHORT and SWEET. Don't need examples unless ABSOLUTELY necessary
- When writing DB queries, for the `customers`, `products` and `features` tables (and others possibly not mentioned here), the primary key when updating is `internal_id`, not `id`
- When using db schemas in Drizzle, import them from '@autumn/shared', and don't do schemas.
- Do NOT use "any" type.
# Testing
- When writing tests, ALWAYS read:

View File

@@ -120,7 +120,7 @@
"recaseai": "^0.0.37",
"resend": "^4.1.1",
"semver": "^7.7.2",
"stripe": "18.4.0-beta.2",
"stripe": "catalog:",
"svix": "^1.45.1",
"tsc-alias": "^1.8.16",
"ws": "^8.18.0",
@@ -159,13 +159,13 @@
"dotenv": "^16.5.0",
"drizzle-kit": "catalog:",
"drizzle-orm": "catalog:",
"stripe": "catalog:",
"yaml": "^2.8.1",
"zod-openapi": "^5.4.1",
},
"devDependencies": {
"@types/bun": "latest",
"@types/node": "^24.0.3",
"@typescript/native-preview": "^7.0.0-dev.20250731.1",
"cross-env": "^7.0.3",
"nodemon": "^3.1.7",
"tsx": "^4.19.4",
@@ -262,6 +262,7 @@
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-router-dom": "^5.3.3",
"@typescript/native-preview": "^7.0.0-dev.20251111.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
@@ -270,12 +271,14 @@
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0",
"vite-tsconfig-paths": "^5.1.4",
},
},
},
"catalog": {
"drizzle-kit": "^0.31.1",
"drizzle-orm": "0.43.1",
"stripe": "18.4.0-beta.2",
},
"packages": {
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
@@ -486,23 +489,23 @@
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.18", "", {}, "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="],
"@biomejs/biome": ["@biomejs/biome@2.3.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.4", "@biomejs/cli-darwin-x64": "2.3.4", "@biomejs/cli-linux-arm64": "2.3.4", "@biomejs/cli-linux-arm64-musl": "2.3.4", "@biomejs/cli-linux-x64": "2.3.4", "@biomejs/cli-linux-x64-musl": "2.3.4", "@biomejs/cli-win32-arm64": "2.3.4", "@biomejs/cli-win32-x64": "2.3.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w=="],
"@biomejs/biome": ["@biomejs/biome@2.3.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.5", "@biomejs/cli-darwin-x64": "2.3.5", "@biomejs/cli-linux-arm64": "2.3.5", "@biomejs/cli-linux-arm64-musl": "2.3.5", "@biomejs/cli-linux-x64": "2.3.5", "@biomejs/cli-linux-x64-musl": "2.3.5", "@biomejs/cli-win32-arm64": "2.3.5", "@biomejs/cli-win32-x64": "2.3.5" }, "bin": { "biome": "bin/biome" } }, "sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ=="],
"@browserbasehq/sdk": ["@browserbasehq/sdk@2.6.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA=="],
@@ -1684,7 +1687,7 @@
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.26", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q=="],
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
@@ -2154,6 +2157,8 @@
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
"goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="],
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
@@ -2182,7 +2187,7 @@
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
"hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="],
"hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="],
@@ -2976,6 +2981,8 @@
"tsc-alias": ["tsc-alias@1.8.16", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -3048,6 +3055,8 @@
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
"vscode-oniguruma": ["vscode-oniguruma@2.0.1", "", {}, "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ=="],
"vscode-textmate": ["vscode-textmate@9.2.1", "", {}, "sha512-eXiUi2yYFv9bdvgrYtJynA7UemCEkpVNE50S9iBBA08LYG5t9+/TB+8IRS/YoYOubCez2OkSyZ1Q12eQMwzbrw=="],
@@ -3938,8 +3947,6 @@
"cloudflare/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
"concurrently/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"convex/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],

View File

@@ -126,9 +126,7 @@ fi
# echo "✅ Copied migration files"
# fi
# Build shared workspace (required for other workspaces)
echo "🔨 Building shared workspace..."
bun -F @autumn/shared build
# Shared workspace is now used directly from source (no build needed)
# # Run database migrations if DATABASE_URL exists
# if grep -q "DATABASE_URL=" server/.env 2>/dev/null; then

View File

@@ -1,64 +0,0 @@
services:
valkey:
image: docker.io/valkey/valkey:8.0
environment:
- ALLOW_EMPTY_PASSWORD=yes
- VALKEY_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
volumes:
- valkey-data:/bitnami/valkey/data
healthcheck:
test: ['CMD', 'redis-cli', '-h', 'localhost', '-p', '6379', 'ping']
interval: 10s
timeout: 5s
retries: 5
ports:
- "6379:6379"
restart: unless-stopped
vite:
build:
context: .
dockerfile: docker/prod.dockerfile
target: vite-prod
ports:
- "3000:3000"
restart: always
server:
environment:
- REDIS_URL=redis://valkey:6379
build:
context: .
dockerfile: docker/prod.dockerfile
target: server-prod
ports:
- "8080:8080"
restart: always
localtunnel:
image: oven/bun:latest
build:
dockerfile: docker/prod.dockerfile
context: .
target: localtunnel
volumes:
- ./server:/app/server
depends_on:
- server
restart: unless-stopped
workers:
environment:
- REDIS_URL=redis://valkey:6379
build:
context: .
dockerfile: docker/prod.dockerfile
target: workers-prod
restart: always
volumes:
valkey-data:
shared-dist:
shared-node-modules:
root-node-modules:

View File

@@ -21,32 +21,25 @@ WORKDIR /app
COPY localtunnel-start.sh ./
CMD ["sh", "localtunnel-start.sh"]
# Stage 2: /shared
FROM base AS shared
COPY shared/ ./shared/
WORKDIR /app/shared
RUN bun run build
CMD ["bun", "dev"]
# Stage 3: /vite
# Stage 2: /vite
FROM base AS vite
COPY --from=shared /app/shared/dist ./shared/dist
COPY shared/ ./shared/
WORKDIR /app/vite
COPY vite/ ./
EXPOSE 3000
CMD ["bun", "dev"]
# Stage 4: /server
# Stage 3: /server
FROM base AS server
COPY --from=shared /app/shared/dist ./shared/dist
COPY shared/ ./shared/
COPY server/ ./server/
WORKDIR /app/server
EXPOSE 8080
CMD ["bun", "dev"]
# Stage 5: Workers
# Stage 4: Workers
FROM base AS workers
COPY --from=shared /app/shared/dist ./shared/dist
COPY shared/ ./shared/
COPY server/ ./server/
WORKDIR /app/server
CMD ["bun", "workers:dev"]

View File

@@ -1,52 +0,0 @@
# ---- Base dependencies ----
FROM oven/bun:latest AS base
WORKDIR /app
RUN bun install -g serve typescript tsc tsc-alias tsx
COPY package.json bun.lock ./
COPY shared/package*.json ./shared/
COPY server/package*.json ./server/
COPY vite/package*.json ./vite/
RUN bun install
FROM base AS localtunnel
WORKDIR /app
COPY localtunnel-start.sh ./
CMD ["sh", "localtunnel-start.sh"]
# ---- Build shared package ----
FROM base AS shared-build
COPY shared/ ./shared/
WORKDIR /app/shared
RUN bun run build
# ---- Build frontend (vite) ----
FROM base AS vite-build
COPY --from=shared-build /app/shared/dist ./shared/dist
COPY . .
WORKDIR /app
RUN bun run build
# ---- Build backend (server) ----
FROM base AS server-build
COPY --from=shared-build /app/shared/dist ./shared/dist
COPY . .
WORKDIR /app
# RUN bun run server:build:bun
# ---- Production frontend image ----
FROM vite-build AS vite-prod
EXPOSE 3000
WORKDIR /app
CMD ["bun", "start"]
FROM server-build AS server-prod
EXPOSE 8080
WORKDIR /app/server
CMD ["bun", "start"]
FROM server-build AS workers-prod
EXPOSE 8080
WORKDIR /app/server
CMD ["bun", "workers"]

View File

@@ -9,6 +9,7 @@
"scripts"
],
"catalog": {
"stripe": "18.4.0-beta.2",
"drizzle-orm": "0.43.1",
"drizzle-kit": "^0.31.1"
}
@@ -16,32 +17,18 @@
"type": "module",
"scripts": {
"dev": "bun scripts/start-dev.js",
"d": "ENV_FILE=.env infisical run --env=dev -- bun scripts/start-dev.js",
"p": "ENV_FILE=.env.prod infisical run --env=prod -- bun scripts/start-dev.js",
"vite:build": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun",
"vite:start": "bun -F @autumn/vite start:bun",
"shared": "bun -F @autumn/shared build",
"server": "cd server && bun start",
"workers": "bun -F @autumn/shared build && bun -F @autumn/server workers",
"cron": "bun -F @autumn/shared build && bun -F @autumn/server cron",
"check": "bun -F @autumn/shared build && bun -F @autumn/server check",
"server:cron": "pnpm -F server cron:start",
"server:check": "NODE_ENV=production pnpm -F server check",
"setup": "node scripts/setup.js",
"setup:test": "bun scripts/setup-test.ts",
"tests": "bun scripts/test.ts",
"setupci": "node scripts/setupci.js",
"replicate": "bun scripts/replicate.ts",
"db:push": " bun -F @autumn/shared db:push",
"db:generate": "bun -F @autumn/shared db:generate",
"db:migrate": " bun -F @autumn/shared db:migrate",
"docker:up": "docker compose -f docker-compose.dev.yml up --build",
"docker:up:unix": "docker compose -f docker-compose.unix.yml up --build",
"docker:up:ci": "docker compose -f docker-compose.ci.yml up --build",
"vite:build:bun": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun",
"vite:start:bun": "bun -F @autumn/shared build && bun -F @autumn/vite start:bun"
"db:migrate": " bun -F @autumn/shared db:migrate"
},
"dependencies": {
"@wooorm/starry-night": "^3.8.0",

View File

@@ -145,27 +145,6 @@ async function startDev() {
// await handlePorts();
}
// Step 1: Build shared package first (initial build)
console.log("\n📦 Building shared package...\n");
const buildShared = spawn("bun", ["run", "build"], {
cwd: "shared",
stdio: "inherit",
shell: true,
});
await new Promise((resolve, reject) => {
buildShared.on("close", (code) => {
if (code !== 0) {
reject(new Error(`Shared package build failed with code ${code}`));
} else {
resolve();
}
});
buildShared.on("error", reject);
});
console.log("\n✅ Shared package built successfully!\n");
// Clear Vite cache to prevent dep optimization issues
const viteCachePath = path.join(
projectRoot,
@@ -180,19 +159,18 @@ async function startDev() {
console.log("🚀 Starting development servers in watch mode...\n");
// Step 2: Start server, workers, and vite first (they'll use the built shared package)
// Start server, workers, and vite (they'll use the shared package source files directly)
const concurrentlyCmd = spawn(
"bunx",
[
"concurrently",
"-n",
"server,workers,vite,shared",
"server,workers,vite",
"-c",
"green,yellow,blue,cyan",
"green,yellow,blue",
`"cd server && SERVER_PORT=${SERVER_PORT} bun dev"`,
`"cd server && bun workers:dev"`,
`"cd vite && VITE_PORT=${VITE_PORT} bun dev"`,
`"cd shared && bun run dev:watch"`,
],
{
stdio: "inherit",

View File

@@ -0,0 +1 @@
../../CLAUDE.md

111
server/CLAUDE.md Normal file
View File

@@ -0,0 +1,111 @@
---
description: Use Bun instead of Node.js, npm, pnpm, or vite.
globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
alwaysApply: false
---
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
// import .css files directly and it works
import './index.css';
import { createRoot } from "react-dom/client";
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.

15
server/README.md Normal file
View File

@@ -0,0 +1,15 @@
# @autumn/server
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.js
```
This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

View File

@@ -1,173 +0,0 @@
import { Redis } from "ioredis";
import { AutumnInt } from "../src/external/autumn/autumnCli.js";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import crypto from "node:crypto";
const client = new Redis("redis://localhost:6379");
// Load Lua scripts
const setCustomerScript = readFileSync(
join(import.meta.dir, "setCustomer.lua"),
"utf-8",
);
const getCustomerScript = readFileSync(
join(import.meta.dir, "getCustomer.lua"),
"utf-8",
);
// Calculate SHA1 hashes for script caching
const setCustomerSha = crypto
.createHash("sha1")
.update(setCustomerScript)
.digest("hex");
const getCustomerSha = crypto
.createHash("sha1")
.update(getCustomerScript)
.digest("hex");
// Load scripts into Redis
await client.script("LOAD", setCustomerScript);
await client.script("LOAD", getCustomerScript);
/**
* Atomically stores a customer object in Redis with features as HSETs
*/
async function setCustomer({ customerData }: { customerData: any }) {
const customerId = customerData.id;
try {
const result = await client.evalsha(
setCustomerSha,
1,
customerId,
JSON.stringify(customerData),
);
return result;
} catch (error: any) {
// If script not found, reload and retry
if (error.message.includes("NOSCRIPT")) {
await client.script("LOAD", setCustomerScript);
return await client.evalsha(
setCustomerSha,
1,
customerId,
JSON.stringify(customerData),
);
}
throw error;
}
}
/**
* Atomically retrieves a customer object from Redis, reconstructing from HSETs
*/
async function getCustomer({ customerId }: { customerId: string }) {
try {
const result = await client.evalsha(getCustomerSha, 1, customerId);
if (!result) {
return null;
}
return JSON.parse(result as string);
} catch (error: any) {
// If script not found, reload and retry
if (error.message.includes("NOSCRIPT")) {
await client.script("LOAD", getCustomerScript);
const retryResult = await client.evalsha(getCustomerSha, 1, customerId);
if (!retryResult) {
return null;
}
return JSON.parse(retryResult as string);
}
throw error;
}
}
/**
* Atomically updates a feature balance using HINCRBYFLOAT
*/
async function updateFeatureBalance({
customerId,
featureId,
amount,
breakdownIndex,
}: {
customerId: string;
featureId: string;
amount: number;
breakdownIndex?: number;
}) {
// Update breakdown-specific balance if index provided
if (breakdownIndex !== undefined) {
const breakdownKey = `customer:${customerId}:features:${featureId}:breakdown:${breakdownIndex}`;
await client.hincrbyfloat(breakdownKey, "balance", amount);
}
// Always update aggregate feature balance
const featureKey = `customer:${customerId}:features:${featureId}`;
await client.hincrbyfloat(featureKey, "balance", amount);
}
/**
* Atomically updates a feature usage using HINCRBYFLOAT
*/
async function updateFeatureUsage({
customerId,
featureId,
amount,
breakdownIndex,
}: {
customerId: string;
featureId: string;
amount: number;
breakdownIndex?: number;
}) {
// Update breakdown-specific usage if index provided
if (breakdownIndex !== undefined) {
const breakdownKey = `customer:${customerId}:features:${featureId}:breakdown:${breakdownIndex}`;
await client.hincrbyfloat(breakdownKey, "usage", amount);
}
// Always update aggregate feature usage
const featureKey = `customer:${customerId}:features:${featureId}`;
await client.hincrbyfloat(featureKey, "usage", amount);
}
const main = async () => {
const autumn = new AutumnInt({ secretKey: process.env.JDEV! });
const customer = await autumn.customers.get("john");
// 1. Set customer
const setStart = performance.now();
await setCustomer({
customerData: customer,
});
const setEnd = performance.now();
console.log(`✓ Stored customer in Redis (${(setEnd - setStart).toFixed(2)}ms)`);
// 2. Get customer
console.time("Get cached customer");
const cachedCustomer = await getCustomer({ customerId: "john" });
console.timeEnd("Get cached customer");
// Compare features
console.log("\n=== Comparison ===");
console.log(`Original credits feature balance: `, cachedCustomer?.features?.credits?.balance);
// Time the decrement of lifetime balance using HDECRBYFLOAT
console.time("Decrement lifetime balance");
await client.hincrbyfloat("customer:john:features:credits", "balance", -1.42513);
console.timeEnd("Decrement lifetime balance");
// Get updated customer
const updatedCustomer = await getCustomer({ customerId: "john" });
console.log('Updated credits feature balance:', updatedCustomer?.features?.credits?.balance);
// Time getting the customer object
console.time("Get base customer");
await client.get("customer:john");
console.timeEnd("Get base customer");
};
await main();
process.exit(0);

View File

@@ -1,72 +0,0 @@
import { AppEnv } from "@autumn/shared";
import {
buildCachedApiCustomerKey,
getCachedApiCustomer,
} from "../src/internal/customers/cusUtils/apiCusCacheUtils/getCachedApiCustomer.js";
import { initDrizzle } from "../src/db/initDrizzle.js";
import { initScript } from "../src/utils/scriptUtils/scriptUtils.js";
import { redis } from "../src/external/redis/initRedis.js";
import { AutumnInt } from "../src/external/autumn/autumnCli.js";
const DEDUCTION_COUNT = 15_000;
const DEDUCTION_AMOUNT = 1;
const logCredits = (label: string, customer: Awaited<ReturnType<typeof getCachedApiCustomer>>) => {
const credits = customer?.features?.credits;
console.log(`\n${label}`);
console.log(` Total Balance: ${credits?.balance ?? "N/A"}`);
console.log(` Monthly Credits: ${credits?.breakdown?.[0]?.balance ?? "N/A"}`);
console.log(` Lifetime Credits: ${credits?.breakdown?.[1]?.balance ?? "N/A"}`);
};
const main = async () => {
const orgId = "org_2s4vfEyYVgFZDlOwcMHjsHR0eef";
const env = AppEnv.Sandbox;
const customerId = "john";
const { db } = initDrizzle();
const { req } = await initScript({ orgId, env });
const autumn = new AutumnInt({
secretKey: process.env.JDEV!,
});
await redis.del(buildCachedApiCustomerKey({ customerId, orgId, env }));
const customerBefore = await getCachedApiCustomer({
ctx: req as any,
customerId,
});
logCredits("📊 Credits Before:", customerBefore);
console.log(`\n⏳ Processing ${DEDUCTION_COUNT.toLocaleString()} deductions...`);
const start = Date.now();
const promises = Array.from({ length: DEDUCTION_COUNT }, () =>
autumn.track({
customer_id: customerId,
feature_id: "credits",
value: DEDUCTION_AMOUNT,
}),
);
await Promise.all(promises);
const elapsed = Date.now() - start;
const customerAfter = await getCachedApiCustomer({
ctx: req as any,
customerId,
});
logCredits("📊 Credits After:", customerAfter);
const deductionDiff = (customerBefore?.features?.credits?.balance ?? 0) - (customerAfter?.features?.credits?.balance ?? 0);
console.log("\n✅ Test Complete!");
console.log(` Time Elapsed: ${elapsed.toLocaleString()}ms`);
console.log(` Total Deducted: ${deductionDiff}`);
console.log(` Avg per Deduction: ${(elapsed / DEDUCTION_COUNT).toFixed(3)}ms\n`);
};
await main();
process.exit(0);

View File

@@ -1,41 +0,0 @@
import { globalBatchingManager } from "../src/internal/balances/track/redisTrackUtils/BatchingManager.js";
/**
* Test script to debug the simplified batchDeduction.lua
* This will show you what the Lua script can retrieve about a feature
*/
async function testLuaDebug() {
// Replace these with real values from your test data
const customerId = "your-customer-id";
const orgId = "your-org-id";
const env = "development";
console.log("Testing Lua script with:");
console.log("- Customer ID:", customerId);
console.log("- Org ID:", orgId);
console.log("- Environment:", env);
console.log("");
try {
const result = await globalBatchingManager.deduct({
customerId,
featureDeductions: [
{ featureId: "credits", amount: 10 },
{ featureId: "api_calls", amount: 5 },
],
orgId,
env,
overageBehavior: "cap",
});
console.log("📦 Batching manager result:");
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error("❌ Error:", error);
}
process.exit(0);
}
testLuaDebug();

1
server/index.js Normal file
View File

@@ -0,0 +1 @@
console.log("Hello via Bun!");

View File

@@ -23,7 +23,9 @@
"parallel-tests:v1": "ENV_FILE=.env infisical run --env=dev -- bun tests/testRunner/runParallelGroups.ts",
"parallel-tests:verbose": "ENV_FILE=.env infisical run --env=dev -- bun tests/testRunner/runParallelGroups.ts --verbose",
"parallel-tests:debug": "ENV_FILE=.env infisical run --env=dev -- bun tests/testRunner/runParallelGroups.ts --debug",
"clear-master": "ENV_FILE=.env infisical run --env=dev -- bun tests/clearMasterOrg.ts"
"clear-master": "ENV_FILE=.env infisical run --env=dev -- bun tests/clearMasterOrg.ts",
"ts": "bunx tsgo --build --noEmit"
},
"mocha": {
"node-option": [
@@ -113,7 +115,7 @@
"recaseai": "^0.0.37",
"resend": "^4.1.1",
"semver": "^7.7.2",
"stripe": "18.4.0-beta.2",
"stripe": "catalog:",
"svix": "^1.45.1",
"tsc-alias": "^1.8.16",
"ws": "^8.18.0",

View File

@@ -36,16 +36,16 @@ import { checkCusSubCorrect } from "./utils/checkUtils/checkCustomerCorrect.js";
const { db } = initDrizzle({ maxConnections: 5 });
let orgSlugs = process.env.ORG_SLUGS!.split(",");
const orgSlugs = process.env.ORG_SLUGS!.split(",");
const skipEmails = process.env.SKIP_EMAILS!.split(",");
const skipIds = [
"cus_2tXCCwC6iyiftgA6ndSo1Ubb2dx",
"DxG668K7uDd0Vahk54YWjvCGVgf2",
];
orgSlugs = ["welcome-back-1747670264"];
let customerId = null;
customerId = "56";
// orgSlugs = ["welcome-back-1747670264"];
// let customerId = null;
const customerId = null;
const getSingleCustomer = async ({
stripeCli,

View File

@@ -18,6 +18,7 @@ import {
type RewardRedemption,
type TrackParams,
} from "@autumn/shared";
import { defaultApiVersion } from "@tests/constants.js";
import type {
CancelParams,
CheckoutParams,
@@ -27,7 +28,6 @@ import type {
Customer,
UsageParams,
} from "autumn-js";
import { defaultApiVersion } from "tests/constants.js";
export default class AutumnError extends Error {
message: string;

View File

@@ -5,7 +5,7 @@ dotenv.config();
import {
type AttachBody,
type CreateEntity,
type CreateEntityParams,
type CreateRewardProgram,
CusExpand,
EntityExpand,
@@ -321,7 +321,7 @@ export class AutumnCliV2 {
create: async (
customerId: string,
entity: CreateEntity | CreateEntity[],
entity: CreateEntityParams | CreateEntityParams[],
) => {
return await this.post(
`/customers/${customerId}/entities?with_autumn_id=true`,

View File

@@ -1,5 +1,5 @@
import { createAnthropic } from "@ai-sdk/anthropic";
import { Feature } from "@autumn/shared";
import type { Feature } from "@autumn/shared";
import { generateObject } from "ai";
import { z } from "zod";
@@ -20,8 +20,8 @@ export const generateFeatureDisplay = async (feature: Feature) => {
</feature>
`;
let { object } = await generateObject({
model: anthropic("claude-3-5-sonnet-latest"),
const { object } = await generateObject({
model: anthropic("claude-haiku-4-5"),
schema: z.object({
singular: z.string(),
plural: z.string(),

View File

@@ -155,7 +155,7 @@ export const handleStripeWebhookEvent = async ({
case "customer.subscription.updated": {
const subscription = event.data.object;
await handleSubscriptionUpdated({
req: ctx as ExtendedRequest,
req: ctx as unknown as ExtendedRequest,
db,
org,
subscription,
@@ -168,7 +168,7 @@ export const handleStripeWebhookEvent = async ({
case "customer.subscription.deleted":
await handleSubDeleted({
req: ctx as ExtendedRequest,
req: ctx as unknown as ExtendedRequest,
stripeCli,
data: event.data.object,
logger,
@@ -178,7 +178,7 @@ export const handleStripeWebhookEvent = async ({
case "checkout.session.completed": {
const checkoutSession = event.data.object;
await handleCheckoutSessionCompleted({
req: ctx as ExtendedRequest,
req: ctx as unknown as ExtendedRequest,
db,
data: checkoutSession,
org,
@@ -196,7 +196,7 @@ export const handleStripeWebhookEvent = async ({
invoiceData: invoice,
env,
event,
req: ctx as ExtendedRequest,
req: ctx as unknown as ExtendedRequest,
});
break;
}
@@ -206,7 +206,7 @@ export const handleStripeWebhookEvent = async ({
stripeCli,
env,
event,
req: ctx as ExtendedRequest,
req: ctx as unknown as ExtendedRequest,
});
break;

View File

@@ -141,7 +141,7 @@ export const getUsageBasedSub = async ({
let usageFeatures: string[] | null = null;
// 1. Check if there's autumn sub
const autumnSub = autumnSubs?.find((sub) => sub.stripe_id == stripeSub.id);
const autumnSub = autumnSubs?.find((sub) => sub.stripe_id === stripeSub.id);
if (autumnSub) {
const containsFeature = autumnSub.usage_features.includes(
feature.internal_id!,
@@ -153,14 +153,14 @@ export const getUsageBasedSub = async ({
try {
usageFeatures = JSON.parse(stripeSub.metadata.usage_features);
} catch (error) {
} catch {
continue;
}
if (
!usageFeatures ||
usageFeatures.find(
(feat: any) => feat.internal_id == feature.internal_id,
(feat: any) => feat.internal_id === feature.internal_id,
) === undefined
) {
continue;

View File

@@ -301,8 +301,6 @@ export const handleInvoiceCreated = async ({
const subId = invoiceToSubId({ invoice });
console.log("HANDLING INVOICE CREATED", invoice.id);
if (subId) {
const activeProducts = await CusProductService.getByStripeSubId({
db,

View File

@@ -4,16 +4,33 @@ import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
/**
* Extracts expand from validated query and sets it in context.
* Must run AFTER versionedValidator so expand has been transformed.
* Uses c.req.query("expand") to access the parsed expand field.
*/
export const expandMiddleware = (): MiddlewareHandler<HonoEnv> => {
return async (c, next) => {
const validatedQuery = c.req.valid("query");
const expand = validatedQuery?.expand || [];
// Query is parsed by queryMiddleware and validated by versionedValidator/validator
// queryStringArray normalizes expand to an array during validation, but we access
// the parsed query which may still be a string for single values
const expandValue = c.req.query("expand");
// queryMiddleware converts "true"/"false" strings to boolean values
// Handle both boolean (from queryMiddleware) and string (fallback) cases
const skipCacheQuery = c.req.query("skip_cache");
const skipCacheValue =
(typeof skipCacheQuery === "boolean" && skipCacheQuery === true) ||
(typeof skipCacheQuery === "string" && skipCacheQuery === "true");
// Normalize to array: undefined -> [], string -> [string], array -> array
const expand: string[] = !expandValue
? []
: Array.isArray(expandValue)
? expandValue
: [expandValue];
const ctx = c.get("ctx");
c.set("ctx", {
...ctx,
expand: Array.isArray(expand) ? expand : [expand],
expand,
skipCache: skipCacheValue,
});
await next();

View File

@@ -65,7 +65,7 @@ export const versionedValidator = ({
throw result.error;
}
validatedData = result.data as Record<string, unknown>;
c.req.addValidatedData(target, result.data);
c.req.addValidatedData(target, validatedData);
} else {
// For other targets, use zValidator
const validatorMiddleware = zValidator(target, schema, (result, _c) => {

View File

@@ -30,6 +30,7 @@ export type RequestContext = {
// Query params
expand: string[];
skipCache: boolean;
};
export type AutumnContext = RequestContext;

View File

@@ -80,7 +80,7 @@ analyticsRouter.post("", (req, res) =>
}
events.data.forEach((event: any) => {
event["period"] = parseInt(format(new Date(event["period"]), "T"));
event.period = parseInt(format(new Date(event.period), "T"));
});
const usageList = events.data.filter(

View File

@@ -404,6 +404,7 @@ export const createFullCusProduct = async ({
isDowngrade,
entities: attachParams.entities,
features: attachParams.features,
prices,
});
// 4. Get new rollovers

View File

@@ -102,7 +102,6 @@ export const handleScheduleFunction2 = async ({
});
const currentPhaseIndex = getCurrentPhaseIndex({
// biome-ignore lint/suspicious/noExplicitAny: ok
schedule: { phases: newItems.phases } as any,
now: attachParams.now,
});

View File

@@ -122,7 +122,7 @@ export const getPricesAndEnts = async ({
optionsInput: optionsInput || [],
features,
prices,
curCusProduct: curMainProduct,
curCusProduct,
}),
prices,
entitlements: getEntsWithFeature({

View File

@@ -7,6 +7,7 @@ import {
intervalToValue,
ProrationBehavior,
} from "@autumn/shared";
import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js";
import { isFreeProduct } from "@/internal/products/productUtils.js";
import { notNullish, nullish } from "@/utils/genUtils.js";
import type { AttachParams } from "../../cusProducts/AttachParams.js";
@@ -16,7 +17,6 @@ import {
attachParamsToProduct,
attachParamToCusProducts,
} from "./convertAttachParams.js";
import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js";
export const intervalsAreSame = ({
attachParams,
@@ -118,10 +118,11 @@ export const getAttachConfig = async ({
let isUpgradingFromDefaultTrial = false;
if (curMainProduct && branch === AttachBranch.Upgrade) {
const product = cusProductToProduct({ cusProduct: curMainProduct });
isUpgradingFromDefaultTrial = isDefaultTrialFullProduct({
product,
skipDefault: true,
}) || false;
isUpgradingFromDefaultTrial =
isDefaultTrialFullProduct({
product,
skipDefault: true,
}) || false;
}
const checkoutFlow =
@@ -172,7 +173,6 @@ export const getAttachConfig = async ({
? attachBody.finalize_invoice!
: true,
requirePaymentMethod: paymentMethodRequired,
};
return { flags, config };

View File

@@ -99,6 +99,10 @@ export const getNewProductPreview = async ({
let trialEnds: number | undefined;
if (config.disableTrial) {
attachParams.freeTrial = null;
}
// Scenario where we update a current sub with new product (so no create sub)
let anchor: number | undefined;
if (mergeSub && !config.disableMerge) {

View File

@@ -1,20 +1,25 @@
import Stripe from "stripe";
import { AttachParams } from "../../cusProducts/AttachParams.js";
import {
type AttachConfig,
CusProductStatus,
cusProductToPrices,
cusProductToProduct,
type FullCusProduct,
} from "@autumn/shared";
import type Stripe from "stripe";
import { getStripeSubItems2 } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
import { AttachConfig, CusProductStatus, FullCusProduct } from "@autumn/shared";
import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { cusProductToPrices, cusProductToProduct } from "@autumn/shared";
import { isArrearPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
import {
findStripeItemForPrice,
subItemInCusProduct,
} from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { mergeNewSubItems } from "./mergeNewSubItems.js";
import { isArrearPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
import { formatPrice } from "@/internal/products/prices/priceUtils.js";
import { getQuantityToRemove } from "./mergeUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { ItemSet } from "@/utils/models/ItemSet.js";
import type { ItemSet } from "@/utils/models/ItemSet.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import type { AttachParams } from "../../cusProducts/AttachParams.js";
import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js";
import { mergeNewSubItems } from "./mergeNewSubItems.js";
import { getQuantityToRemove } from "./mergeUtils.js";
export const getCusProductsToRemove = ({
attachParams,
@@ -53,12 +58,12 @@ export const getCusProductsToRemove = ({
cusProductsToRemove.push(curScheduledProduct);
}
// 1. If product is an add on, and there's current same, add it
else if (curSameProduct) {
// 2. If cancelling, and same product exists (add on / main), add it.
else if (curSameProduct && attachParams.fromCancel) {
cusProductsToRemove.push(curSameProduct);
}
// 2. If product is a main product, add curMain
// 3. If product is a main product, add curMain
else if (!product.is_add_on && curMainProduct) {
cusProductsToRemove.push(curMainProduct);
}
@@ -100,8 +105,7 @@ export const paramsToSubItems = async ({
removeCusProducts?: FullCusProduct[];
addItemSet?: ItemSet;
}) => {
const { logger } = req;
let curSubItems = sub?.items.data || [];
const curSubItems = sub?.items.data || [];
const itemSet = notNullish(addItemSet)
? addItemSet!
@@ -120,7 +124,7 @@ export const paramsToSubItems = async ({
cusProductsToRemove.map((cp) => cp.product.name),
);
let newSubItems = mergeNewSubItems({
const newSubItems = mergeNewSubItems({
itemSet,
curSubItems,
});
@@ -165,7 +169,7 @@ export const paramsToSubItems = async ({
allCusProducts.some((cp) => {
if (cp.id === cusProduct.id) return false;
if (cp.status == CusProductStatus.Scheduled) return false;
if (cp.status === CusProductStatus.Scheduled) return false;
return subItemInCusProduct({
cusProduct: cp,
@@ -177,7 +181,7 @@ export const paramsToSubItems = async ({
}
if (
itemSet.subItems.some((si) => si.price == existingSubItem.price?.id)
itemSet.subItems.some((si) => si.price === existingSubItem.price?.id)
) {
continue;
}

View File

@@ -33,9 +33,10 @@ export const subToNewSchedule = async ({
attachParams,
includeCanceled: true,
});
console.log(
`REMOVING CUS PRODUCTS: ${cusProductsToRemove.map((cp) => `${cp.product.id} (E: ${cp.entity_id})`).join(", ")}`,
); // See mergedDowngrade5.test.ts -- if there's no schedule and creating one, might need to include canceled cus products
);
const res = await paramsToScheduleItems({
req,

View File

@@ -158,6 +158,7 @@ export const handleCancelProduct = async ({
replaceables: [],
entities: fullCus.entities,
features: req.features,
fromCancel: true,
},
config: getDefaultAttachConfig(),
skipInsertCusProduct,

View File

@@ -15,6 +15,7 @@ import {
import { getEntOptions } from "@/internal/products/prices/priceUtils.js";
import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js";
import { notNullish, nullish } from "@/utils/genUtils.js";
import { isOneOff } from "../../../../products/productUtils.js";
import {
getRelatedCusPrice,
getUnlimitedAndUsageAllowed,
@@ -161,6 +162,7 @@ export const getExistingUsages = ({
export const addExistingUsagesToCusEnts = ({
cusEnts,
entitlements,
prices,
curCusProduct,
carryExistingUsages = false,
printLogs = false,
@@ -170,6 +172,7 @@ export const addExistingUsagesToCusEnts = ({
}: {
cusEnts: CustomerEntitlement[];
entitlements: EntitlementWithFeature[];
prices: Price[];
curCusProduct: FullCusProduct;
carryExistingUsages?: boolean;
printLogs?: boolean;
@@ -177,9 +180,7 @@ export const addExistingUsagesToCusEnts = ({
entities: Entity[];
features: Feature[];
}) => {
if (isDowngrade) {
return cusEnts;
}
if (isDowngrade || isOneOff(prices)) return cusEnts;
const existingUsages = getExistingUsages({
curCusProduct,

View File

@@ -63,13 +63,6 @@ export const initNextResetAt = ({
entitlement.interval_count || 1,
).getTime();
// console.log(`--------------------------------`);
// console.log(`Interval: `, entitlement.interval);
// console.log(`Interval count: `, entitlement.interval_count);
// console.log(`Now: `, formatUnixToDateTime(now));
// console.log(`Next reset at: `, formatUnixToDateTime(nextResetAtCalculated));
// console.log(`Anchor to unix: `, formatUnixToDateTime(anchorToUnix));
// If anchorToUnix, align next reset at to anchorToUnix...
if (
anchorToUnix &&

View File

@@ -38,17 +38,15 @@ export const buildCachedApiCustomerKey = ({
export const getCachedApiCustomer = async ({
ctx,
customerId,
skipCache = false,
skipEntityMerge = false,
source,
}: {
ctx: AutumnContext;
customerId: string;
skipCache?: boolean;
skipEntityMerge?: boolean; // If true, returns only customer's own features (no entity merging)
source?: string;
}): Promise<{ apiCustomer: ApiCustomer; legacyData: CustomerLegacyData }> => {
const { org, env, db } = ctx;
const { org, env, db, skipCache } = ctx;
const getExpandedApiCustomer = async () => {
// await redis.del(

View File

@@ -166,12 +166,12 @@ export const getApiBalance = ({
}),
);
const totalUnused = sumValues(
cusEnts.map((cusEnt) => {
const { unused } = getCusEntBalance({ cusEnt, entityId });
return unused;
}),
);
// const totalUnused = sumValues(
// cusEnts.map((cusEnt) => {
// const { unused } = getCusEntBalance({ cusEnt, entityId });
// return unused;
// }),
// );
const totalAdditionalBalance = sumValues(
cusEnts.map((cusEnt) => {
@@ -183,25 +183,6 @@ export const getApiBalance = ({
const totalMaxPurchase = sumValues(
cusEnts.map((cusEnt) => cusEntToMaxPurchase({ cusEnt })),
);
// const totalUsageLimit = sumValues(
// cusEnts.map((cusEnt) => cusEntToUsageLimit({ cusEnt, entityId })),
// );
// const totalIncludedUsage = sumValues(
// cusEnts.map((cusEnt) => cusEntToIncludedUsage({ cusEnt, entityId })),
// );
// const totalIncludedUsageWithRollovers = sumValues(
// cusEnts.map((cusEnt) =>
// cusEntToIncludedUsage({ cusEnt, entityId, withRollovers: true }),
// ),
// );
// const totalUsage = new Decimal(totalIncludedUsageWithRollovers)
// .add(totalAdjustment)
// .sub(totalBalanceWithRollovers)
// .sub(totalUnused)
// .toNumber();
// 1. Granted balance
const totalGrantedBalanceWithRollovers = sumValues(
@@ -257,10 +238,6 @@ export const getApiBalance = ({
const reset = cusEntsToReset({ cusEnts, feature });
const rollovers = cusEntsToRollovers({ cusEnts, entityId });
// console.log(
// `Feature: ${feature.id}, Total Max Purchase: ${totalMaxPurchase}, Usage Allowed: ${usageAllowed}`,
// );
const { data: apiBalance, error } = ApiBalanceSchema.safeParse({
feature: ctx.expand.includes(CusExpand.BalanceFeature)
? apiFeature

View File

@@ -82,13 +82,13 @@ export const handleUpdateEntitlement = async (req: any, res: any) => {
env: req.env,
});
if (balance < 0 && !cusEnt.usage_allowed) {
throw new RecaseError({
message: "Entitlement does not allow usage",
code: ErrCode.InvalidRequest,
statusCode: StatusCodes.BAD_REQUEST,
});
}
// if (balance < 0 && !cusEnt.usage_allowed) {
// throw new RecaseError({
// message: "Entitlement does not allow usage",
// code: ErrCode.InvalidRequest,
// statusCode: StatusCodes.BAD_REQUEST,
// });
// }
if (cusEnt.unlimited) {
throw new RecaseError({
@@ -115,7 +115,7 @@ export const handleUpdateEntitlement = async (req: any, res: any) => {
},
toDeduct: deducted,
addAdjustment: true,
allowNegativeBalance: cusEnt.usage_allowed || false,
allowNegativeBalance: true,
entityId: entity_id,
},
);

View File

@@ -24,6 +24,18 @@ export const handleFeatureTypeChanged = async ({
const { linkedEntitlements, entitlements, prices, creditSystems, cusEnts } =
objectsUsingFeature;
// Cannot switch between credit system <-> metered / boolean
if (
(feature.type === FeatureType.CreditSystem &&
newType !== FeatureType.CreditSystem) ||
(feature.type !== FeatureType.CreditSystem &&
newType === FeatureType.CreditSystem)
) {
throw new RecaseError({
message: `Cannot change type of feature ${feature.id} from ${feature.type} to ${newType}`,
});
}
if (cusEnts.length > 0) {
throw new RecaseError({
message: `Cannot change type of feature ${feature.id} because it has been attached to a customer before`,

View File

@@ -60,11 +60,17 @@ export class InvoiceService {
with: {
customer: {
with: {
org: true,
org: {
with: {
master: true,
},
},
},
},
},
})) as Invoice & { customer: Customer & { org: Organization } };
})) as Invoice & {
customer: Customer & { org: Organization & { master: Organization } };
};
}
static async list({

View File

@@ -2,12 +2,13 @@ import {
type EntitlementWithFeature,
type Feature,
type FullProduct,
getProductItemDisplay,
type Organization,
type Price,
} from "@autumn/shared";
import { mapToProductItems } from "@/internal/products/productV2Utils.js";
import { isFeatureItem } from "../products/product-items/productItemUtils/getItemType.js";
import { getProductItemDisplay } from "../products/productUtils/productResponseUtils/getProductItemDisplay.js";
// import { getProductItemDisplay } from "../products/productUtils/productResponseUtils/getProductItemDisplay.js";
export const buildInvoiceMemo = async ({
org,

View File

@@ -63,10 +63,12 @@ mainRouter.use(
try {
const org = invoice.customer.org;
const env = invoice.customer.env;
const stripeCli = createStripeCli({
org,
env,
});
const stripeInvoice = await stripeCli.invoices.retrieve(
invoice.stripe_id,
);

View File

@@ -22,16 +22,12 @@ export const getReplaceables = ({
}
const numReplaceables = prevOverage - newOverage;
const newReplaceables = Array.from(
{ length: numReplaceables },
(_, i) =>
({
id: generateId("rep"),
cus_ent_id: cusEnt.id,
created_at: Date.now(),
delete_next_cycle: deleteNextCycle,
}) satisfies InsertReplaceable,
);
const newReplaceables = Array.from({ length: numReplaceables }, (_, i) => ({
id: generateId("rep"),
cus_ent_id: cusEnt.id,
created_at: Date.now(),
delete_next_cycle: deleteNextCycle,
}));
return newReplaceables;
};

View File

@@ -16,6 +16,26 @@ export const shouldBillNow = (onIncrease: OnIncrease | OnDecrease) => {
);
};
export const shouldProrateDowngradeNow = ({
onIncrease,
onDecrease,
}: {
onIncrease: OnIncrease;
onDecrease: OnDecrease;
}) => {
if (
onDecrease === OnDecrease.NoProrations ||
onDecrease === OnDecrease.None
) {
return false;
}
return (
onIncrease === OnIncrease.ProrateImmediately ||
onIncrease === OnIncrease.BillImmediately
);
};
export const shouldProrate = (onIncrease?: OnIncrease | OnDecrease) => {
if (!onIncrease) {
return true;

View File

@@ -256,7 +256,8 @@ export const toFeatureAndPrice = ({
if (shouldProrate(onDecrease) || onDecrease === OnDecrease.Prorate) {
onDecrease =
onIncrease === OnIncrease.ProrateImmediately
onIncrease === OnIncrease.ProrateImmediately ||
onIncrease === OnIncrease.BillImmediately
? OnDecrease.ProrateImmediately
: OnDecrease.ProrateNextCycle;
}

View File

@@ -1,5 +1,5 @@
import { ErrCode } from "@autumn/shared";
import { type Response } from "express";
import type { Response } from "express";
import { nanoid } from "nanoid";
import { CacheManager } from "@/utils/cacheUtils/CacheManager.js";
import RecaseError from "@/utils/errorUtils.js";

View File

@@ -74,10 +74,9 @@ const initWorker = ({ id, db }: { id: number; db: DrizzleCli }) => {
if (actionHandlers.includes(job.name as JobName)) {
await runActionHandlerTask({
queue: queue,
job,
logger: workerLogger,
db,
jobName: job.name as JobName,
payload: job.data,
ctx,
});
return;
}

View File

@@ -17,10 +17,10 @@ import type Stripe from "stripe";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { getUsageBasedSub } from "@/external/stripe/stripeSubUtils.js";
import { getRelatedCusPrice } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
import { getBillingType } from "@/internal/products/prices/priceUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { cusProductToSub } from "../internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import { handleProratedDowngrade } from "./arrearProratedUsage/handleProratedDowngrade.js";
import { handleProratedUpgrade } from "./arrearProratedUsage/handleProratedUpgrade.js";
@@ -115,13 +115,18 @@ export const adjustAllowance = async ({
logger.info(`Customer: ${customer.name}, Org: ${org.slug}`);
const stripeCli = createStripeCli({ org, env });
const sub = await getUsageBasedSub({
db,
const sub = await cusProductToSub({
cusProduct,
stripeCli,
subIds: cusProduct.subscription_ids!,
feature: affectedFeature,
});
// const sub = await getUsageBasedSub({
// db,
// stripeCli,
// subIds: cusProduct.subscription_ids!,
// feature: affectedFeature,
// });
if (!sub) {
logger.error("adjustAllowance: no usage-based sub found");
return { newReplaceables: null, invoice: null, deletedReplaceables: null };

View File

@@ -5,6 +5,7 @@ import {
getFeatureInvoiceDescription,
type InsertReplaceable,
OnDecrease,
OnIncrease,
type Organization,
type Product,
type UsagePriceConfig,
@@ -21,12 +22,12 @@ import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/pric
import {
shouldBillNow,
shouldProrate,
shouldProrateDowngradeNow,
} from "@/internal/products/prices/priceUtils/prorationConfigUtils.js";
import { roundUsage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
import { formatUnixToDate } from "@/utils/genUtils.js";
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
import { getUsageFromBalance } from "../adjustAllowance.js";
export const createDowngradeProrationInvoice = async ({
org,
cusPrice,
@@ -38,6 +39,7 @@ export const createDowngradeProrationInvoice = async ({
newRoundedUsage,
feature,
product,
onIncrease,
onDecrease,
logger,
}: {
@@ -51,6 +53,7 @@ export const createDowngradeProrationInvoice = async ({
newRoundedUsage: number;
feature: Feature;
product: Product;
onIncrease: OnIncrease;
onDecrease: OnDecrease;
logger: any;
}) => {
@@ -102,7 +105,12 @@ export const createDowngradeProrationInvoice = async ({
await stripeCli.invoiceItems.create(invoiceItem);
let invoice = null;
if (shouldBillNow(onDecrease)) {
if (
shouldProrateDowngradeNow({
onIncrease,
onDecrease,
})
) {
const { invoice: finalInvoice } = await createAndFinalizeInvoice({
stripeCli,
paymentMethod: null,
@@ -196,6 +204,9 @@ export const handleProratedDowngrade = async ({
}),
feature,
product,
onIncrease:
cusPrice.price.proration_config?.on_increase ||
OnIncrease.ProrateImmediately,
onDecrease,
logger,
});

View File

@@ -1,9 +1,14 @@
import "dotenv/config";
import { invitation } from "@autumn/shared";
import { betterAuth } from "better-auth";
import { betterAuth, type User } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin, emailOTP, organization } from "better-auth/plugins";
import {
admin,
emailOTP,
type Organization,
organization,
} from "better-auth/plugins";
import { eq } from "drizzle-orm";
import { db } from "@/db/initDrizzle.js";
import { logger } from "@/external/logtail/logtailUtils.js";
@@ -104,7 +109,11 @@ export const auth = betterAuth({
}),
organization({
async sendInvitationEmail(data) {
async sendInvitationEmail(data: {
id: string;
email: string;
organization: Organization;
}) {
const inviteLink = `${process.env.CLIENT_URL}/accept?id=${data.id}`;
await sendInvitationEmail({
email: data.email,
@@ -135,7 +144,13 @@ export const auth = betterAuth({
organizationCreation: {
disabled: false,
afterCreate: async ({ organization, user }) => {
afterCreate: async ({
organization,
user,
}: {
organization: Organization;
user: User;
}) => {
await afterOrgCreated({ org: organization, user });
},
},

View File

@@ -1,7 +1,7 @@
import { ApiVersion, type ProductV2 } from "@autumn/shared";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { TestContext } from "tests/utils/testInitUtils/createTestContext.js";
import { createProducts } from "@tests/utils/productUtils.js";
import type { TestContext } from "@tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "@tests/utils/testProductUtils/testProductUtils.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
export const initProductsV0 = async ({

View File

@@ -3,7 +3,7 @@ import dotenv from "dotenv";
dotenv.config();
import { AppEnv } from "@autumn/shared";
import { clearOrg, setupOrg } from "tests/utils/setup.js";
import { clearOrg, setupOrg } from "@tests/utils/setup.js";
import { initDrizzle } from "@/db/initDrizzle.js";
import {
advanceProducts,
@@ -33,16 +33,6 @@ describe("Initialize org for tests", () => {
await setupOrg({
orgId: this.org.id,
env: DEFAULT_ENV,
features: { ...features, ...creditSystems } as any,
products: {
...products,
...advanceProducts,
...attachProducts,
...oneTimeProducts,
...entityProducts,
} as any,
rewards: { ...rewards } as any,
rewardTriggers: { ...referralPrograms } as any,
});
console.log("--------------------------------");

View File

@@ -1,13 +1,13 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
ApiVersion,
type CheckResponse,
type CheckResponseV0,
type CheckResponseV1,
SuccessCode,
} from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import chalk from "chalk";
import { TestFeature } from "tests/setup/v2Features.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
@@ -44,7 +44,7 @@ describe(`${chalk.yellowBright("temp: temporary script for testing")}`, () => {
const res = (await autumnV1.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
})) as unknown as CheckResponse;
})) as unknown as CheckResponseV1;
expect(res).toStrictEqual({
allowed: false,

View File

@@ -0,0 +1,118 @@
import { beforeAll, describe, test } from "bun:test";
import {
type AppEnv,
LegacyVersion,
OnDecrease,
OnIncrease,
type Organization,
} from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import chalk from "chalk";
import type { Stripe } from "stripe";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {
constructArrearProratedItem,
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";
// UNCOMMENT FROM HERE
const addOn = constructProduct({
type: "pro",
isAddOn: true,
items: [
// constructFeatureItem({ featureId: TestFeature.Words, includedUsage: 100 }),
constructArrearProratedItem({
featureId: TestFeature.Users,
includedUsage: 1,
pricePerUnit: 10,
config: {
on_increase: OnIncrease.BillImmediately,
on_decrease: OnDecrease.Prorate,
},
}),
],
});
describe(`${chalk.yellowBright("temp1: Testing add ons")}`, () => {
const customerId = "temp1";
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let stripeCli: Stripe;
let testClockId: string;
let curUnix: number;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [addOn],
prefix: customerId,
});
testClockId = testClockId1!;
});
test("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: addOn.id,
});
await autumn.entities.create(customerId, [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
]);
await autumn.entities.delete(customerId, "1");
// await autumn.attach({
// customer_id: customerId,
// product_id: addOn.id,
// });
// await autumn.attach({
// customer_id: customerId,
// product_id: addOn.id,
// });
});
// test("should cancel one add on", async () => {
// await autumn.cancel({
// customer_id: customerId,
// product_id: addOn.id,
// });
// await expectSubToBeCorrect({
// customerId,
// db,
// org,
// env,
// });
// });
});

View File

@@ -1,234 +0,0 @@
import {
type AppEnv,
type Customer,
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 { setupBefore } from "tests/before.js";
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 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 { initCustomer } from "@/utils/scriptUtils/initCustomer.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).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
stripeId: customer.stripe_id!,
});
expect(cusDiscount).to.exist;
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(
Math.round(couponAmount * 100),
`Expected stripe cus to have coupon amount ${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();
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const res = await initCustomer({
customerId,
org,
env,
db,
autumn: this.autumnJs,
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
products: [pro],
orgId: org.id,
env,
db,
autumn,
});
testClockId = res.testClockId;
customer = res.customer;
});
// CYCLE 0
it("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).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(cusDiscount).to.exist;
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
});
it("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
it("should run another cycle and have correct invoice + coupon amount", async () => {
const res = await simulateOneCycle({
customerId,
db,
org,
env,
stripeCli,
autumn,
testClockId,
couponAmount,
curUnix,
});
});
},
);

View File

@@ -7,22 +7,22 @@ import {
type Organization,
RewardType,
} from "@autumn/shared";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
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 { createReward } from "tests/utils/productUtils.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 { createReward } from "@tests/utils/productUtils.js";
import {
advanceTestClock,
completeCheckoutForm,
getDiscount,
} from "tests/utils/stripeUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
} from "@tests/utils/stripeUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.js";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";

View File

@@ -1,197 +0,0 @@
import {
type AppEnv,
CouponDurationType,
type CreateReward,
LegacyVersion,
type Organization,
RewardType,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import { Decimal } from "decimal.js";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.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, createReward } from "tests/utils/productUtils.js";
import { completeCheckoutForm, getDiscount } from "tests/utils/stripeUtils.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.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;
before(async function () {
await setupBefore(this);
org = this.org;
env = this.env;
db = this.db;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
});
testClockId = testClockId1;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
orgId: this.org.id,
env: this.env,
db: this.db,
autumn,
products: [pro],
});
await createReward({
orgId: org.id,
env,
db,
autumn,
reward,
productId: pro.id,
onlyUsage: true,
});
});
// CYCLE 0
it("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,
});
});
it("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).to.equal(fixedPrice);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
});
// CYCLE 1
it("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).to.equal(basePrice);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(
Math.round(couponAmount * 100),
);
});
},
);

View File

@@ -11,18 +11,18 @@ 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 ctx from "tests/utils/testInitUtils/createTestContext.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, createReward } from "@tests/utils/productUtils.js";
import { completeCheckoutForm, getDiscount } from "@tests/utils/stripeUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
} from "@tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";

View File

@@ -1,176 +0,0 @@
import {
type AppEnv,
CouponDurationType,
type CreateReward,
LegacyVersion,
type Organization,
RewardType,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
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 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 { initCustomer } from "@/utils/scriptUtils/initCustomer.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;
before(async function () {
await setupBefore(this);
org = this.org;
env = this.env;
db = this.db;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = testClockId1;
addPrefixToProducts({
products: [pro, oneOff],
prefix: testCase,
});
await createProducts({
orgId: this.org.id,
env: this.env,
db: this.db,
autumn,
products: [pro, oneOff],
});
await createReward({
orgId: org.id,
env,
db,
autumn,
reward,
productId: pro.id,
});
});
// CYCLE 0
it("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).to.equal(basePrice - couponAmount);
});
it("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).to.equal(basePrice - couponAmount);
expect(invoice.product_ids).to.include(oneOff.id);
});
it("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).to.equal(3);
const basePrice = getBasePrice({ product: oneOff });
for (let i = 0; i < 2; i++) {
const invoice = customer.invoices![i];
expect(invoice.total).to.equal(basePrice - couponAmount);
expect(invoice.product_ids).to.include(oneOff.id);
}
});
});

View File

@@ -9,14 +9,14 @@ import {
} from "@autumn/shared";
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 ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { expectAttachCorrect } from "@tests/utils/expectUtils/expectAttach.js";
import { createProducts, createReward } from "@tests/utils/productUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
} from "@tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {

View File

@@ -3,14 +3,14 @@ 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 { 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";

View File

@@ -3,11 +3,11 @@ import { LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.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";

View File

@@ -3,14 +3,14 @@ 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 { 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";

View File

@@ -3,14 +3,14 @@ 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 { 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";

View File

@@ -3,11 +3,11 @@ import { LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
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 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 ctx from "@tests/utils/testInitUtils/createTestContext.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";

View File

@@ -3,14 +3,14 @@ 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 { 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";

View File

@@ -3,13 +3,13 @@ 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 { 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";

View File

@@ -3,10 +3,10 @@ import { LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { addDays, addMonths } from "date-fns";
import type Stripe from "stripe";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
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 ctx from "@tests/utils/testInitUtils/createTestContext.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
import {

View File

@@ -3,13 +3,13 @@ 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 { 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";

View File

@@ -3,16 +3,16 @@ 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 { 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";
} 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";

View File

@@ -3,14 +3,14 @@ import { LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.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 ctx from "tests/utils/testInitUtils/createTestContext.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
} from "@tests/utils/expectUtils/expectScheduleUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.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";

View File

@@ -3,16 +3,16 @@ 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 { 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";
} 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";

View File

@@ -3,11 +3,11 @@ 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 { 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";

View File

@@ -3,9 +3,9 @@ import { LegacyVersion } from "@autumn/shared";
import type { Customer } from "autumn-js";
import chalk from "chalk";
import type Stripe from "stripe";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";

View File

@@ -3,11 +3,11 @@ 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 { 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";

View File

@@ -1,23 +1,22 @@
// Manual customer creation - not using initCustomer to control test clock properly
import { beforeAll, describe, it } from "bun:test";
import {
type AppEnv,
CusProductStatus,
FreeTrialDuration,
LegacyVersion,
type Organization,
ProductItemInterval,
} from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import chalk from "chalk";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
import {
defaultTrialFree,
defaultTrialPro,
setupDefaultTrialBefore,
} from "./defaultTrialBefore.test.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";
// Case 1: ✅
// Pro product with default trial exists alongside a free default product
@@ -28,35 +27,66 @@ import {
// Pro product with default trial exists alone
// -> Creating a new customer should attach the pro product with default trial
const defaultTrialPro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 1500,
interval: ProductItemInterval.Month,
}),
],
isDefault: true,
forcePaidDefault: true,
id: "defaultTrial_pro",
group: "defaultTrial",
type: "pro",
freeTrial: {
length: 7,
duration: FreeTrialDuration.Day,
unique_fingerprint: false,
card_required: false,
},
});
const defaultTrialFree = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
interval: ProductItemInterval.Month,
}),
],
id: "defaultTrial_free",
group: "defaultTrial",
type: "free",
isDefault: true,
});
const testCase = "defaultTrial1";
describe(`${chalk.yellowBright(`advanced/${testCase}: ensure default trials are attached when creating a customer`)}`, () => {
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 = Math.floor(new Date().getTime() / 1000);
beforeAll(async () => {
// Products must be initialized BEFORE customer creation for default products
await initProductsV0({
ctx,
products: [defaultTrialPro, defaultTrialFree],
prefix: testCase,
customerId,
});
beforeAll(async function () {
await setupBefore(this);
await setupDefaultTrialBefore({});
const { autumnJs } = this;
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
const res = await initCustomerV2({
autumn: autumnJs,
customerId: testCase,
db,
org,
env,
const res = await initCustomerV3({
ctx,
customerId,
withTestClock: true,
});
testClockID = res.testClockId;
stripeCli = ctx.stripeCli;
});
it("should create a customer with the paid default trial", async () => {

View File

@@ -1,59 +1,76 @@
// Manual customer creation - not using initCustomer to control test clock properly
import { beforeAll, describe, it } from "bun:test";
import {
type AppEnv,
CusProductStatus,
FreeTrialDuration,
LegacyVersion,
type Organization,
ProductItemInterval,
} from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import chalk from "chalk";
import { addDays, addHours } from "date-fns";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
import {
defaultTrialPro,
setupDefaultTrialBefore,
} from "./defaultTrialBefore.test.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";
// 2.2:
// -> Creating a new customer with a payment method should attach the pro product with default trial
// --> Advancing the test clock should cancel the trial and attach the pro product
const defaultTrialPro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 1500,
interval: ProductItemInterval.Month,
}),
],
isDefault: true,
forcePaidDefault: true,
id: "defaultTrial_pro",
group: "defaultTrial",
type: "pro",
freeTrial: {
length: 7,
duration: FreeTrialDuration.Day,
unique_fingerprint: false,
card_required: false,
},
});
const testCase = "defaultTrial2";
describe(`${chalk.yellowBright(`advanced/${testCase}: ensure trial transitions into full product if payment method is valid`)}`, () => {
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 = Math.floor(new Date().getTime() / 1000);
beforeAll(async () => {
// Products must be initialized BEFORE customer creation for default products
await initProductsV0({
ctx,
products: [defaultTrialPro],
prefix: testCase,
customerId,
});
beforeAll(async function () {
await setupBefore(this);
await setupDefaultTrialBefore({});
const { autumnJs } = this;
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
const res = await initCustomerV2({
autumn: autumnJs,
customerId: testCase,
db,
org,
env,
const res = await initCustomerV3({
ctx,
customerId,
attachPm: "success",
withTestClock: true,
});
testClockID = res.testClockId;
stripeCli = ctx.stripeCli;
});
it("should create a customer with the paid default trial", async () => {

View File

@@ -1,59 +1,76 @@
// Manual customer creation - not using initCustomer to control test clock properly
import { beforeAll, describe, it } from "bun:test";
import {
type AppEnv,
CusProductStatus,
FreeTrialDuration,
LegacyVersion,
type Organization,
ProductItemInterval,
} from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import chalk from "chalk";
import { addDays, addHours } from "date-fns";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
import {
defaultTrialPro,
setupDefaultTrialBefore,
} from "./defaultTrialBefore.test.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";
// 2.3:
// -> Creating a new customer with a fake payment method should attach the pro product with default trial
// --> Advancing the test clock should cancel the trial and attach the free product
const defaultTrialPro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 1500,
interval: ProductItemInterval.Month,
}),
],
isDefault: true,
forcePaidDefault: true,
id: "defaultTrial_pro",
group: "defaultTrial",
type: "pro",
freeTrial: {
length: 7,
duration: FreeTrialDuration.Day,
unique_fingerprint: false,
card_required: false,
},
});
const testCase = "defaultTrial3";
describe(`${chalk.yellowBright(`advanced/${testCase}: ensure trials cancel with bad payment method`)}`, () => {
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 = Math.floor(new Date().getTime() / 1000);
beforeAll(async () => {
// Products must be initialized BEFORE customer creation for default products
await initProductsV0({
ctx,
products: [defaultTrialPro],
prefix: testCase,
customerId,
});
beforeAll(async function () {
await setupBefore(this);
await setupDefaultTrialBefore({});
const { autumnJs } = this;
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
const res = await initCustomerV2({
autumn: autumnJs,
customerId: testCase,
db,
org,
env,
const res = await initCustomerV3({
ctx,
customerId,
attachPm: "fail",
withTestClock: true,
});
testClockID = res.testClockId;
stripeCli = ctx.stripeCli;
});
it("should create a customer with the paid default trial", async () => {

View File

@@ -1,57 +0,0 @@
import {
FreeTrialDuration,
LegacyVersion,
ProductItemInterval,
} from "@autumn/shared";
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";
export const defaultTrialPro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 1500,
interval: ProductItemInterval.Month,
}),
],
isDefault: true,
forcePaidDefault: true,
id: "defaultTrial_pro",
group: "defaultTrial",
type: "pro",
freeTrial: {
length: 7,
duration: FreeTrialDuration.Day,
unique_fingerprint: false,
card_required: false,
},
});
export const defaultTrialFree = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
interval: ProductItemInterval.Month,
}),
],
id: "defaultTrial_free",
group: "defaultTrial",
type: "free",
isDefault: true,
});
export const setupDefaultTrialBefore = async ({}: {}) => {
const autumn = new AutumnInt({ version: LegacyVersion.v1_2 });
for (const product of [defaultTrialPro, defaultTrialFree]) {
const res = await autumn.products.get(product.id);
if (res.code === "product_not_found") {
try {
await autumn.products.create(product);
} catch (error) {}
}
}
};

View File

@@ -2,10 +2,10 @@ import { beforeAll, describe, expect, test } from "bun:test";
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { TestFeature } from "tests/setup/v2Features.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { cusProductToSub } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";

View File

@@ -1,13 +1,13 @@
import { beforeAll, describe, expect, test } from "bun:test";
import { ApiVersion, type AppEnv, LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { TestFeature } from "tests/setup/v2Features.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import {
getPrepaidCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";

View File

@@ -7,14 +7,14 @@ import {
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { features } from "tests/global.js";
import { setupBefore } from "@tests/before.js";
import { features } from "@tests/global.js";
import {
getPrepaidCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import { createProducts } from "@tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructFeaturePriceItem } from "@/internal/products/product-items/productItemUtils.js";

View File

@@ -1,13 +1,13 @@
import { beforeAll, describe, expect, test } from "bun:test";
import { ApiVersion, type AppEnv, LegacyVersion } from "@autumn/shared";
import chalk from "chalk";
import { TestFeature } from "tests/setup/v2Features.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import {
getLifetimeFreeCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";

View File

@@ -8,14 +8,14 @@ import {
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { features } from "tests/global.js";
import { setupBefore } from "@tests/before.js";
import { features } from "@tests/global.js";
import {
getLifetimeFreeCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import { createProduct } from "tests/utils/productUtils.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import { createProduct } from "@tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {

View File

@@ -4,13 +4,13 @@ import { beforeAll, describe, expect, test } from "bun:test";
import { ApiVersion, type AppEnv } from "@autumn/shared";
import chalk from "chalk";
import { addMonths } from "date-fns";
import { TestFeature } from "tests/setup/v2Features.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import {
getLifetimeFreeCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";

View File

@@ -9,15 +9,15 @@ import {
import { expect } from "chai";
import chalk from "chalk";
import { addMonths } from "date-fns";
import { setupBefore } from "tests/before.js";
import { features } from "tests/global.js";
import { setupBefore } from "@tests/before.js";
import { features } from "@tests/global.js";
import {
getLifetimeFreeCusEnt,
getUsageCusEnt,
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
} from "@tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.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 {

View File

@@ -10,9 +10,9 @@ import {
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { setupBefore } from "@tests/before.js";
import { expectProductV1Attached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";

View File

@@ -10,9 +10,9 @@ import {
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 { 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";

View File

@@ -10,9 +10,9 @@ import {
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { setupBefore } from "@tests/before.js";
import { expectProductV1Attached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { CusService } from "@/internal/customers/CusService.js";

View File

@@ -10,9 +10,9 @@ import {
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 { 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";

View File

@@ -10,9 +10,9 @@ import {
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { setupBefore } from "@tests/before.js";
import { expectProductV1Attached } from "@tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { CusService } from "@/internal/customers/CusService.js";

View File

@@ -10,9 +10,9 @@ import {
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 { 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";

View File

@@ -10,12 +10,12 @@
// import { assert } from "chai";
// import chalk from "chalk";
// import type { Stripe } from "stripe";
// import { setupBefore } from "tests/before.js";
// import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
// import { setupBefore } from "@tests/before.js";
// import { expectProductV1Attached } from "@tests/utils/expectUtils/expectProductAttached.js";
// import {
// advanceTestClock,
// completeCheckoutForm,
// } from "tests/utils/stripeUtils.js";
// } from "@tests/utils/stripeUtils.js";
// import type { DrizzleCli } from "@/db/initDrizzle.js";
// import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
// import { CusService } from "@/internal/customers/CusService.js";

View File

@@ -13,12 +13,12 @@
// 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 { expectProductV1Attached } from "@tests/utils/expectUtils/expectProductAttached.js";
// import {
// advanceTestClock,
// completeCheckoutForm,
// } from "tests/utils/stripeUtils.js";
// import ctx from "tests/utils/testInitUtils/createTestContext.js";
// } 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";

View File

@@ -15,11 +15,11 @@ import {
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 { createReferralProgram } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { timeout } from "@tests/utils/genUtils.js";
import { createReferralProgram } from "@tests/utils/productUtils.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 { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";

View File

@@ -16,11 +16,11 @@ import {
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 { createReferralProgram } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { timeout } from "@tests/utils/genUtils.js";
import { createReferralProgram } from "@tests/utils/productUtils.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 { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";

View File

@@ -11,10 +11,10 @@ import {
RewardType,
} from "@autumn/shared";
import chalk from "chalk";
import { TestFeature } from "tests/setup/v2Features.js";
import { timeout } from "tests/utils/genUtils.js";
import { createReferralProgram } from "tests/utils/productUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "@tests/setup/v2Features.js";
import { timeout } from "@tests/utils/genUtils.js";
import { createReferralProgram } from "@tests/utils/productUtils.js";
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";

Some files were not shown because too many files have changed in this diff Show More