From ef5e44fb84ccdf6d154c7e4917bd24e13f23f227 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Wed, 7 Jan 2026 17:46:05 +0000 Subject: [PATCH] added .vscode --- .gitignore | 2 +- .vscode/settings.json | 4 + .vscode/tasks.json | 30 +++ .../multi-entity-free-to-paid.test.ts | 16 +- .../multi-product/schedules-from-paid.test.ts | 190 +++++++++--------- .../preview-total/preview-proration.test.ts | 11 +- .../utils/expectCustomerProductCorrect.ts | 4 +- 7 files changed, 149 insertions(+), 108 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 377c6a6e3..c18eecc97 100644 --- a/.gitignore +++ b/.gitignore @@ -100,7 +100,7 @@ supabase/ # 2. Load files .claude/ -.vscode/ + migration.sh stat.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..2e082b494 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "postman.settings.dotenv-detection-notification-visibility": false, + "typescript.preferences.importModuleSpecifier": "non-relative" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..e44c6266d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run Test Pattern", + "type": "shell", + "command": "bun test ${relativeFile} -t \"${input:testPattern}\"", + "problemMatcher": [] + }, + { + "label": "Run Describe at Cursor", + "type": "shell", + "command": "bun test ${relativeFile} --timeout 0 -t \"$(bun scripts/testScripts/getDescribeAtCursor.ts ${file} ${lineNumber})\"", + "problemMatcher": [] + }, + { + "label": "Run Current Test File", + "type": "shell", + "command": "bun test ${relativeFile}", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "testPattern", + "type": "promptString", + "description": "Test name pattern" + } + ] +} diff --git a/server/tests/billing/update-subscription/multi-product/multi-entity-free-to-paid.test.ts b/server/tests/billing/update-subscription/multi-product/multi-entity-free-to-paid.test.ts index bbc877da2..7fddef2b4 100644 --- a/server/tests/billing/update-subscription/multi-product/multi-entity-free-to-paid.test.ts +++ b/server/tests/billing/update-subscription/multi-product/multi-entity-free-to-paid.test.ts @@ -1,6 +1,7 @@ import { expect, test } from "bun:test"; import { expectCustomerFeatureCorrect } from "@tests/billing/utils/expectCustomerFeatureCorrect"; import { expectCustomerInvoiceCorrect } from "@tests/billing/utils/expectCustomerInvoiceCorrect"; +import { expectProductActive } from "@tests/billing/utils/expectCustomerProductCorrect"; import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; @@ -55,9 +56,10 @@ test.concurrent(`${chalk.yellowBright("multi-entity-free-to-paid: entity 2 upgra items: [messagesItem, priceItem], }); - // Verify entity 2's feature + // Verify entity 2 has the updated product const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); + await expectProductActive({ customer: entity2Data, productId: free.id }); expectCustomerFeatureCorrect({ customer: entity2Data, featureId: TestFeature.Messages, @@ -149,9 +151,10 @@ test.concurrent(`${chalk.yellowBright("multi-entity-free-to-paid: base + consuma ], }); - // Verify entity 2's features + // Verify entity 2 has the updated product const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); + await expectProductActive({ customer: entity2Data, productId: free.id }); expectCustomerFeatureCorrect({ customer: entity2Data, featureId: TestFeature.Messages, @@ -226,9 +229,10 @@ test.concurrent(`${chalk.yellowBright("multi-entity-free-to-paid: annual price") items: [messagesItem, annualPriceItem], }); - // Verify entity 2's feature + // Verify entity 2 has the updated product const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); + await expectProductActive({ customer: entity2Data, productId: free.id }); expectCustomerFeatureCorrect({ customer: entity2Data, featureId: TestFeature.Messages, @@ -305,9 +309,10 @@ test.concurrent(`${chalk.yellowBright("multi-entity-free-to-paid: annual mid-cyc items: [messagesItem, annualPriceItem], }); - // Verify entity 2's feature + // Verify entity 2 has the updated product const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); + await expectProductActive({ customer: entity2Data, productId: free.id }); expectCustomerFeatureCorrect({ customer: entity2Data, featureId: TestFeature.Messages, @@ -384,9 +389,10 @@ test.concurrent(`${chalk.yellowBright("multi-entity-free-to-paid: monthly mid-cy items: [messagesItem, priceItem], }); - // Verify entity 2's feature + // Verify entity 2 has the updated product const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); + await expectProductActive({ customer: entity2Data, productId: free.id }); expectCustomerFeatureCorrect({ customer: entity2Data, featureId: TestFeature.Messages, diff --git a/server/tests/billing/update-subscription/multi-product/schedules-from-paid.test.ts b/server/tests/billing/update-subscription/multi-product/schedules-from-paid.test.ts index c3667ceca..b90c3c981 100644 --- a/server/tests/billing/update-subscription/multi-product/schedules-from-paid.test.ts +++ b/server/tests/billing/update-subscription/multi-product/schedules-from-paid.test.ts @@ -1,4 +1,9 @@ -import { expect, test } from "bun:test"; +import { test } from "bun:test"; +import { + expectProductActive, + expectProductCanceled, + expectProductNotPresent, +} from "@tests/billing/utils/expectCustomerProductCorrect"; import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; @@ -43,10 +48,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: cancel entity 1, update en customerId, entities[0].id, ); - const entity1Product = entity1AfterCancel.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - expect(entity1Product?.canceled_at).toBeDefined(); + await expectProductCanceled({ + customer: entity1AfterCancel, + productId: `${customerId}_pro`, + }); // Entity 2 updates pro's items (change price) const newPriceItem = items.monthlyPrice({ price: 30 }); // $30/mo instead of $20 @@ -60,10 +65,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: cancel entity 1, update en // Verify entity 2 has updated items const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); - const entity2Product = entity2Data.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - expect(entity2Product).toBeDefined(); + await expectProductActive({ + customer: entity2Data, + productId: `${customerId}_pro`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -89,15 +94,14 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: cancel entity 1, update en entities[1].id, ); - const entity1ProAfter = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - const entity2ProAfter = entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - - expect(entity1ProAfter).toBeUndefined(); - expect(entity2ProAfter).toBeDefined(); + await expectProductNotPresent({ + customer: entity1AfterCycle, + productId: `${customerId}_pro`, + }); + await expectProductActive({ + customer: entity2AfterCycle, + productId: `${customerId}_pro`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -166,10 +170,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: downgrade entity 1, update // Verify entity 2's premium still active with new price const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); - const entity2Premium = entity2Data.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - expect(entity2Premium).toBeDefined(); + await expectProductActive({ + customer: entity2Data, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -196,19 +200,18 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: downgrade entity 1, update entities[1].id, ); - const entity1Premium = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - const entity1Pro = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - const entity2PremiumAfter = entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - - expect(entity1Premium).toBeUndefined(); - expect(entity1Pro).toBeDefined(); - expect(entity2PremiumAfter).toBeDefined(); + await expectProductNotPresent({ + customer: entity1AfterCycle, + productId: `${customerId}_premium`, + }); + await expectProductActive({ + customer: entity1AfterCycle, + productId: `${customerId}_pro`, + }); + await expectProductActive({ + customer: entity2AfterCycle, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -287,10 +290,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: annual + monthly downgrade // Verify entity 3's premium still active const entity3Data = await autumnV1.entities.get(customerId, entities[2].id); - const entity3Premium = entity3Data.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - expect(entity3Premium).toBeDefined(); + await expectProductActive({ + customer: entity3Data, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -323,26 +326,26 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: annual + monthly downgrade ); // Entity 1 should still have premium-annual (annual cycle not over) - const entity1PremiumAnnual = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium-annual`, - ); - expect(entity1PremiumAnnual).toBeDefined(); + await expectProductActive({ + customer: entity1AfterCycle, + productId: `${customerId}_premium-annual`, + }); // Entity 2 should be on pro now - const entity2Premium = entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - const entity2Pro = entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - expect(entity2Premium).toBeUndefined(); - expect(entity2Pro).toBeDefined(); + await expectProductNotPresent({ + customer: entity2AfterCycle, + productId: `${customerId}_premium`, + }); + await expectProductActive({ + customer: entity2AfterCycle, + productId: `${customerId}_pro`, + }); // Entity 3 should still have premium - const entity3PremiumAfter = entity3AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - expect(entity3PremiumAfter).toBeDefined(); + await expectProductActive({ + customer: entity3AfterCycle, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -377,16 +380,14 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: update to free item remove // Verify both entities have pro const entity1Before = await autumnV1.entities.get(customerId, entities[0].id); const entity2Before = await autumnV1.entities.get(customerId, entities[1].id); - expect( - entity1Before.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ), - ).toBeDefined(); - expect( - entity2Before.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ), - ).toBeDefined(); + await expectProductActive({ + customer: entity1Before, + productId: `${customerId}_pro`, + }); + await expectProductActive({ + customer: entity2Before, + productId: `${customerId}_pro`, + }); // Entity 2 updates Pro's items to be free (no price items, only feature) await autumnV1.subscriptions.update({ @@ -398,10 +399,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: update to free item remove // Entity 2's subscription should now be free (removed from paid sub) const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); - const entity2Product = entity2Data.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - expect(entity2Product).toBeDefined(); + await expectProductActive({ + customer: entity2Data, + productId: `${customerId}_pro`, + }); // Should only have 1 subscription (entity 1's pro) await expectSubToBeCorrect({ @@ -429,16 +430,14 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: update to free item remove entities[1].id, ); - expect( - entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ), - ).toBeDefined(); - expect( - entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ), - ).toBeDefined(); + await expectProductActive({ + customer: entity1AfterCycle, + productId: `${customerId}_pro`, + }); + await expectProductActive({ + customer: entity2AfterCycle, + productId: `${customerId}_pro`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -507,10 +506,10 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: downgrade + update to free // Entity 2's subscription should now be free const entity2Data = await autumnV1.entities.get(customerId, entities[1].id); - const entity2Premium = entity2Data.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - expect(entity2Premium).toBeDefined(); + await expectProductActive({ + customer: entity2Data, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, @@ -538,19 +537,18 @@ test.concurrent(`${chalk.yellowBright("schedules-p2p: downgrade + update to free entities[1].id, ); - const entity1Premium = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - const entity1Pro = entity1AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_pro`, - ); - const entity2PremiumAfter = entity2AfterCycle.products?.find( - (p: { id?: string }) => p.id === `${customerId}_premium`, - ); - - expect(entity1Premium).toBeUndefined(); - expect(entity1Pro).toBeDefined(); - expect(entity2PremiumAfter).toBeDefined(); + await expectProductNotPresent({ + customer: entity1AfterCycle, + productId: `${customerId}_premium`, + }); + await expectProductActive({ + customer: entity1AfterCycle, + productId: `${customerId}_pro`, + }); + await expectProductActive({ + customer: entity2AfterCycle, + productId: `${customerId}_premium`, + }); await expectSubToBeCorrect({ db: ctx.db, diff --git a/server/tests/billing/update-subscription/preview-total/preview-proration.test.ts b/server/tests/billing/update-subscription/preview-total/preview-proration.test.ts index ccea54423..10b302a18 100644 --- a/server/tests/billing/update-subscription/preview-total/preview-proration.test.ts +++ b/server/tests/billing/update-subscription/preview-total/preview-proration.test.ts @@ -1,6 +1,5 @@ -import { applyProration, priceToLineAmount } from "@autumn/shared"; import { expect, test } from "bun:test"; -import { TestFeature } from "@tests/setup/v2Features.js"; +import { applyProration, type Price, priceToLineAmount } from "@autumn/shared"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; @@ -34,7 +33,10 @@ test.concurrent(`${chalk.yellowBright("preview-total: mid-cycle free to paid pro const customer = await autumnV1.customers.get(customerId); const subscription = customer.products?.[0]; - if (!subscription?.current_period_start || !subscription?.current_period_end) { + if ( + !subscription?.current_period_start || + !subscription?.current_period_end + ) { throw new Error("Missing billing period on subscription"); } @@ -54,7 +56,7 @@ test.concurrent(`${chalk.yellowBright("preview-total: mid-cycle free to paid pro // Calculate expected amount manually const baseAmount = priceToLineAmount({ - price: priceItem.price, + price: priceItem.price as unknown as Price, multiplier: 1, }); @@ -74,4 +76,3 @@ test.concurrent(`${chalk.yellowBright("preview-total: mid-cycle free to paid pro expect(preview.total).toBe(expectedAmount); }); - diff --git a/server/tests/billing/utils/expectCustomerProductCorrect.ts b/server/tests/billing/utils/expectCustomerProductCorrect.ts index 55c1e42eb..7a7c1deb5 100644 --- a/server/tests/billing/utils/expectCustomerProductCorrect.ts +++ b/server/tests/billing/utils/expectCustomerProductCorrect.ts @@ -38,7 +38,9 @@ export const expectCustomerProductCorrect = async ({ } if (!product) { - throw new Error(`Product ${productId} not found but expected state: ${state}`); + throw new Error( + `Product ${productId} not found but expected state: ${state}`, + ); } if (state === "active") {