From 77aa04463bb037d4535cb971f2a06f5fd8b8ac30 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Wed, 6 May 2026 15:39:00 +0100 Subject: [PATCH] Fix routed Redis reset test coverage --- .../reset/persist-free-overage-on.test.ts | 432 +++++++----------- .../utils/cusProductUtils/resetTestUtils.ts | 38 +- .../testInitUtils/createSubOrgTestContext.ts | 11 + .../utils/testInitUtils/createTestContext.ts | 23 +- .../tests/utils/testInitUtils/initScenario.ts | 3 + 5 files changed, 245 insertions(+), 262 deletions(-) diff --git a/server/tests/integration/balances/reset/persist-free-overage-on.test.ts b/server/tests/integration/balances/reset/persist-free-overage-on.test.ts index c2acc749d..378a39f38 100644 --- a/server/tests/integration/balances/reset/persist-free-overage-on.test.ts +++ b/server/tests/integration/balances/reset/persist-free-overage-on.test.ts @@ -3,7 +3,6 @@ import { type ApiCustomer, type ApiCustomerV3, customerEntitlements, - type OrgConfig, } from "@autumn/shared"; import { resetAndGetCusEnt } from "@tests/balances/track/rollovers/rolloverTestUtils.js"; import { findCustomerEntitlement } from "@tests/balances/utils/findCustomerEntitlement.js"; @@ -20,7 +19,6 @@ import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; import { eq } from "drizzle-orm"; import { db } from "@/db/initDrizzle.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; const setBalanceInDb = async ({ cusEntId, @@ -35,33 +33,14 @@ const setBalanceInDb = async ({ .where(eq(customerEntitlements.id, cusEntId)); }; -const enablePersistFreeOverage = async ({ - orgId, - orgConfig, -}: { - orgId: string; - orgConfig: OrgConfig; -}) => { - await OrgService.update({ - db, - orgId, - updates: { config: { ...orgConfig, persist_free_overage: true } }, +const persistFreeOverageOrg = ({ slug }: { slug: string }) => + s.platform.create({ + userEmail: `persist-overage-${slug}-${Math.random() + .toString(36) + .slice(2, 8)}@autumn.test`, + configOverrides: { persist_free_overage: true }, + setupDefaultFeatures: true, }); -}; - -const disablePersistFreeOverage = async ({ - orgId, - orgConfig, -}: { - orgId: string; - orgConfig: OrgConfig; -}) => { - await OrgService.update({ - db, - orgId, - updates: { config: { ...orgConfig, persist_free_overage: false } }, - }); -}; // ───────────────────────────────────────────────────────────────── // 1. Lazy reset (DB path) @@ -73,41 +52,33 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (DB): lazy reset deduc const { customerId, autumnV2, ctx } = await initScenario({ customerId: "persist-ovg-on-db", - setup: [s.customer({ testClock: false }), s.products({ list: [free] })], + setup: [ + persistFreeOverageOrg({ slug: "db" }), + s.customer({ testClock: false }), + s.products({ list: [free] }), + ], actions: [s.attach({ productId: free.id })], }); - await enablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, + const cusEnt = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(cusEnt).toBeDefined(); + + await setBalanceInDb({ cusEntId: cusEnt!.id, balance: -100 }); + await expireCusEntForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, }); - try { - const cusEnt = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEnt).toBeDefined(); - - await setBalanceInDb({ cusEntId: cusEnt!.id, balance: -100 }); - await expireCusEntForReset({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - - const after = await autumnV2.customers.get(customerId, { - skip_cache: "true", - }); - expect(after.balances[TestFeature.Messages].current_balance).toBe(0); - expect(after.balances[TestFeature.Messages].usage).toBe(100); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + const after = await autumnV2.customers.get(customerId, { + skip_cache: "true", + }); + expect(after.balances[TestFeature.Messages].current_balance).toBe(0); + expect(after.balances[TestFeature.Messages].usage).toBe(100); }); // ───────────────────────────────────────────────────────────────── @@ -120,59 +91,51 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (cache): lazy reset de const { customerId, autumnV2, ctx } = await initScenario({ customerId: "persist-ovg-on-cache", - setup: [s.customer({ testClock: false }), s.products({ list: [free] })], + setup: [ + persistFreeOverageOrg({ slug: "cache" }), + s.customer({ testClock: false }), + s.products({ list: [free] }), + ], actions: [s.attach({ productId: free.id })], }); - await enablePersistFreeOverage({ + await autumnV2.customers.get(customerId); + + const cusEnt = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(cusEnt).toBeDefined(); + + await setBalanceInDb({ cusEntId: cusEnt!.id, balance: -50 }); + await setCachedCusEntField({ orgId: ctx.org.id, - orgConfig: ctx.org.config, + env: ctx.env, + customerId, + cusEntId: cusEnt!.id, + field: "balance", + value: -50, + }); + await setCachedSubjectBalanceField({ + ctx, + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Messages, + customerEntitlementId: cusEnt!.id, + field: "balance", + value: -50, + }); + await expireCusEntForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, }); - try { - await autumnV2.customers.get(customerId); - - const cusEnt = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEnt).toBeDefined(); - - await setBalanceInDb({ cusEntId: cusEnt!.id, balance: -50 }); - await setCachedCusEntField({ - orgId: ctx.org.id, - env: ctx.env, - customerId, - cusEntId: cusEnt!.id, - field: "balance", - value: -50, - }); - await setCachedSubjectBalanceField({ - orgId: ctx.org.id, - env: ctx.env, - customerId, - featureId: TestFeature.Messages, - customerEntitlementId: cusEnt!.id, - field: "balance", - value: -50, - redisV2: ctx.redisV2, - }); - await expireCusEntForReset({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - - const after = await autumnV2.customers.get(customerId); - expect(after.balances[TestFeature.Messages].current_balance).toBe(50); - expect(after.balances[TestFeature.Messages].usage).toBe(50); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + const after = await autumnV2.customers.get(customerId); + expect(after.balances[TestFeature.Messages].current_balance).toBe(50); + expect(after.balances[TestFeature.Messages].usage).toBe(50); }); // ───────────────────────────────────────────────────────────────── @@ -185,7 +148,11 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (cron): cron reset ded const { customerId, ctx, customer } = await initScenario({ customerId: "persist-ovg-on-cron", - setup: [s.customer({ testClock: false }), s.products({ list: [free] })], + setup: [ + persistFreeOverageOrg({ slug: "cron" }), + s.customer({ testClock: false }), + s.products({ list: [free] }), + ], actions: [s.attach({ productId: free.id })], }); @@ -219,41 +186,33 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (no overage): positive const { customerId, autumnV2, ctx } = await initScenario({ customerId: "persist-ovg-on-positive", - setup: [s.customer({ testClock: false }), s.products({ list: [free] })], + setup: [ + persistFreeOverageOrg({ slug: "positive" }), + s.customer({ testClock: false }), + s.products({ list: [free] }), + ], actions: [s.attach({ productId: free.id })], }); - await enablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, + const cusEnt = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(cusEnt).toBeDefined(); + + await setBalanceInDb({ cusEntId: cusEnt!.id, balance: 50 }); + await expireCusEntForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, }); - try { - const cusEnt = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEnt).toBeDefined(); - - await setBalanceInDb({ cusEntId: cusEnt!.id, balance: 50 }); - await expireCusEntForReset({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - - const after = await autumnV2.customers.get(customerId, { - skip_cache: "true", - }); - expect(after.balances[TestFeature.Messages].current_balance).toBe(100); - expect(after.balances[TestFeature.Messages].usage).toBe(0); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + const after = await autumnV2.customers.get(customerId, { + skip_cache: "true", + }); + expect(after.balances[TestFeature.Messages].current_balance).toBe(100); + expect(after.balances[TestFeature.Messages].usage).toBe(0); }); // ───────────────────────────────────────────────────────────────── @@ -270,6 +229,7 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (per-entity): each ent const { customerId, ctx } = await initScenario({ customerId: "persist-ovg-on-entity", setup: [ + persistFreeOverageOrg({ slug: "entity" }), s.customer({ testClock: false }), s.products({ list: [free] }), s.entities({ count: 2, featureId: TestFeature.Users }), @@ -277,56 +237,43 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (per-entity): each ent actions: [s.attach({ productId: free.id })], }); - await enablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, + const cusEnt = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, }); - ctx.org.config = { ...ctx.org.config, persist_free_overage: true }; + expect(cusEnt).toBeDefined(); + expect(cusEnt!.entities).toBeDefined(); - try { - const cusEnt = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEnt).toBeDefined(); - expect(cusEnt!.entities).toBeDefined(); + const entities = { ...cusEnt!.entities! }; + const entityIds = Object.keys(entities); + expect(entityIds.length).toBe(2); - const entities = { ...cusEnt!.entities! }; - const entityIds = Object.keys(entities); - expect(entityIds.length).toBe(2); + entities[entityIds[0]].balance = -50; + entities[entityIds[0]].adjustment = 0; + entities[entityIds[1]].balance = 30; + entities[entityIds[1]].adjustment = 0; - entities[entityIds[0]].balance = -50; - entities[entityIds[0]].adjustment = 0; - entities[entityIds[1]].balance = 30; - entities[entityIds[1]].adjustment = 0; + await db + .update(customerEntitlements) + .set({ entities }) + .where(eq(customerEntitlements.id, cusEnt!.id)); + await expireCusEntForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); - await db - .update(customerEntitlements) - .set({ entities }) - .where(eq(customerEntitlements.id, cusEnt!.id)); - await expireCusEntForReset({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); + const cusEntAfter = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(cusEntAfter).toBeDefined(); + expect(cusEntAfter!.entities).toBeDefined(); - const cusEntAfter = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEntAfter).toBeDefined(); - expect(cusEntAfter!.entities).toBeDefined(); - - expect(cusEntAfter!.entities![entityIds[0]].balance).toBe(50); - expect(cusEntAfter!.entities![entityIds[1]].balance).toBe(100); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + expect(cusEntAfter!.entities![entityIds[0]].balance).toBe(50); + expect(cusEntAfter!.entities![entityIds[1]].balance).toBe(100); }); // ───────────────────────────────────────────────────────────────── @@ -343,6 +290,7 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (per-entity mixed): di const { customerId, ctx } = await initScenario({ customerId: "persist-ovg-on-ent-mix", setup: [ + persistFreeOverageOrg({ slug: "entity-mixed" }), s.customer({ testClock: false }), s.products({ list: [free] }), s.entities({ count: 2, featureId: TestFeature.Users }), @@ -350,56 +298,43 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (per-entity mixed): di actions: [s.attach({ productId: free.id })], }); - await enablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, + const cusEnt = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, }); - ctx.org.config = { ...ctx.org.config, persist_free_overage: true }; + expect(cusEnt).toBeDefined(); + expect(cusEnt!.entities).toBeDefined(); - try { - const cusEnt = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEnt).toBeDefined(); - expect(cusEnt!.entities).toBeDefined(); + const entities = { ...cusEnt!.entities! }; + const entityIds = Object.keys(entities); + expect(entityIds.length).toBe(2); - const entities = { ...cusEnt!.entities! }; - const entityIds = Object.keys(entities); - expect(entityIds.length).toBe(2); + entities[entityIds[0]].balance = -30; + entities[entityIds[0]].adjustment = 0; + entities[entityIds[1]].balance = -200; + entities[entityIds[1]].adjustment = 0; - entities[entityIds[0]].balance = -30; - entities[entityIds[0]].adjustment = 0; - entities[entityIds[1]].balance = -200; - entities[entityIds[1]].adjustment = 0; + await db + .update(customerEntitlements) + .set({ entities }) + .where(eq(customerEntitlements.id, cusEnt!.id)); + await expireCusEntForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); - await db - .update(customerEntitlements) - .set({ entities }) - .where(eq(customerEntitlements.id, cusEnt!.id)); - await expireCusEntForReset({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); + const cusEntAfter = await findCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(cusEntAfter).toBeDefined(); + expect(cusEntAfter!.entities).toBeDefined(); - const cusEntAfter = await findCustomerEntitlement({ - ctx, - customerId, - featureId: TestFeature.Messages, - }); - expect(cusEntAfter).toBeDefined(); - expect(cusEntAfter!.entities).toBeDefined(); - - expect(cusEntAfter!.entities![entityIds[0]].balance).toBe(70); - expect(cusEntAfter!.entities![entityIds[1]].balance).toBe(-100); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + expect(cusEntAfter!.entities![entityIds[0]].balance).toBe(70); + expect(cusEntAfter!.entities![entityIds[1]].balance).toBe(-100); }); // ───────────────────────────────────────────────────────────────── @@ -418,6 +353,7 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (prepaid): invoice res const { customerId, autumnV1, ctx, testClockId } = await initScenario({ customerId: "persist-ovg-on-prepaid", setup: [ + persistFreeOverageOrg({ slug: "prepaid" }), s.customer({ paymentMethod: "success" }), s.products({ list: [pro] }), ], @@ -430,38 +366,24 @@ test.concurrent(`${chalk.yellowBright("persist overage ON (prepaid): invoice res ], }); - // Enable flag BEFORE advancing, so the webhook handler sees it in DB - await enablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, + // After attach: prepaid balance = 200 (quantity=200, billingUnits=100, so 2*100=200), lifetime = 50 + // Total messages balance = 200 + 50 = 250 + // After track 300: deducted 300 from messages + // The prepaid cusEnt's balance should be negative (overage) + // On advance, handlePrepaidPrices resets prepaid with persistFreeOverage + await advanceToNextInvoice({ + stripeCli: ctx.stripeCli, + testClockId: testClockId!, + withPause: true, }); - try { - // After attach: prepaid balance = 200 (quantity=200, billingUnits=100, so 2*100=200), lifetime = 50 - // Total messages balance = 200 + 50 = 250 - // After track 300: deducted 300 from messages - // The prepaid cusEnt's balance should be negative (overage) - // On advance, handlePrepaidPrices resets prepaid with persistFreeOverage + const customer = await autumnV1.customers.get(customerId); - await advanceToNextInvoice({ - stripeCli: ctx.stripeCli, - testClockId: testClockId!, - withPause: true, - }); - - const customer = await autumnV1.customers.get(customerId); - - // Lifetime messages (50) should be untouched by the prepaid reset - // Prepaid: was 200, used 300 -> overage of some amount on the prepaid cusEnt - // After reset with persist_free_overage: new prepaid balance = 200 - overage - // The exact split depends on deduction order, so just verify the feature exists - // and the balance is less than the full 250 (200 prepaid + 50 lifetime) - expect(customer.features[TestFeature.Messages]).toBeDefined(); - expect(customer.features[TestFeature.Messages].balance).toBeLessThan(250); - } finally { - await disablePersistFreeOverage({ - orgId: ctx.org.id, - orgConfig: ctx.org.config, - }); - } + // Lifetime messages (50) should be untouched by the prepaid reset + // Prepaid: was 200, used 300 -> overage of some amount on the prepaid cusEnt + // After reset with persist_free_overage: new prepaid balance = 200 - overage + // The exact split depends on deduction order, so just verify the feature exists + // and the balance is less than the full 250 (200 prepaid + 50 lifetime) + expect(customer.features[TestFeature.Messages]).toBeDefined(); + expect(customer.features[TestFeature.Messages].balance).toBeLessThan(250); }); diff --git a/server/tests/utils/cusProductUtils/resetTestUtils.ts b/server/tests/utils/cusProductUtils/resetTestUtils.ts index 759c4b195..8e250cbce 100644 --- a/server/tests/utils/cusProductUtils/resetTestUtils.ts +++ b/server/tests/utils/cusProductUtils/resetTestUtils.ts @@ -7,11 +7,26 @@ import { findCustomerEntitlement } from "@tests/balances/utils/findCustomerEntit import type { TestContext } from "@tests/utils/testInitUtils/createTestContext.js"; import { eq } from "drizzle-orm"; import type { Redis } from "ioredis"; -import { redis } from "@/external/redis/initRedis.js"; +import { getCtxWithCustomerRedis } from "@/external/redis/customerRedisRouting.js"; +import { redis, waitForRedisReady } from "@/external/redis/initRedis.js"; import { CusService } from "@/internal/customers/CusService.js"; import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; import { buildFullCustomerCacheKey } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; +const getRoutedRedisForCustomer = async ({ + ctx, + customerId, +}: { + ctx: TestContext; + customerId: string; +}): Promise => { + const { ctx: routedCtx } = getCtxWithCustomerRedis({ ctx, customerId }); + await waitForRedisReady(routedCtx.redisV2, "customer-redis", 5000).catch( + () => undefined, + ); + return routedCtx.redisV2; +}; + /** * Update next_reset_at for a specific cusEnt in the Redis FullCustomer cache. * Reads the cached blob, finds the cusEnt by ID, then uses JSON.SET on the exact path. @@ -70,6 +85,7 @@ export const setCachedCusEntField = async ({ /** Patch next_reset_at on a SubjectBalance in the V2 shared balance hash. */ export const setCachedSubjectBalanceField = async ({ + ctx, orgId, env, customerId, @@ -79,6 +95,7 @@ export const setCachedSubjectBalanceField = async ({ value, redisV2, }: { + ctx?: TestContext; orgId: string; env: string; customerId: string; @@ -86,8 +103,15 @@ export const setCachedSubjectBalanceField = async ({ customerEntitlementId: string; field: string; value: number | string | null; - redisV2: Redis; + redisV2?: Redis; }): Promise => { + const targetRedisV2 = + redisV2 ?? + (ctx ? await getRoutedRedisForCustomer({ ctx, customerId }) : null); + if (!targetRedisV2) { + throw new Error("setCachedSubjectBalanceField requires redisV2 or ctx"); + } + const balanceKey = buildSharedFullSubjectBalanceKey({ orgId, env, @@ -95,12 +119,12 @@ export const setCachedSubjectBalanceField = async ({ featureId, }); - const raw = await redisV2.hget(balanceKey, customerEntitlementId); + const raw = await targetRedisV2.hget(balanceKey, customerEntitlementId); if (!raw) return; const subjectBalance = JSON.parse(raw); subjectBalance[field] = value; - await redisV2.hset( + await targetRedisV2.hset( balanceKey, customerEntitlementId, JSON.stringify(subjectBalance), @@ -136,6 +160,7 @@ export const expireCusEntForReset = async ({ } const pastTime = pastTimeMs ?? Date.now() - 1000; + const routedRedisV2 = await getRoutedRedisForCustomer({ ctx, customerId }); // Update Postgres await ctx.db @@ -162,7 +187,7 @@ export const expireCusEntForReset = async ({ customerEntitlementId: cusEnt.id, field: "next_reset_at", value: pastTime, - redisV2: ctx.redisV2, + redisV2: routedRedisV2, }); return cusEnt; @@ -200,6 +225,7 @@ export const expireAllCusEntsForReset = async ({ } const pastTime = pastTimeMs ?? Date.now() - 1000; + const routedRedisV2 = await getRoutedRedisForCustomer({ ctx, customerId }); for (const cusEnt of cusEnts) { await ctx.db @@ -224,7 +250,7 @@ export const expireAllCusEntsForReset = async ({ customerEntitlementId: cusEnt.id, field: "next_reset_at", value: pastTime, - redisV2: ctx.redisV2, + redisV2: routedRedisV2, }); } diff --git a/server/tests/utils/testInitUtils/createSubOrgTestContext.ts b/server/tests/utils/testInitUtils/createSubOrgTestContext.ts index 13f92f136..e21a63209 100644 --- a/server/tests/utils/testInitUtils/createSubOrgTestContext.ts +++ b/server/tests/utils/testInitUtils/createSubOrgTestContext.ts @@ -5,6 +5,7 @@ import { LATEST_VERSION, type OrgConfig, } from "@autumn/shared"; +import { getFeatures } from "@tests/setup/v2Features.js"; import { initDrizzle } from "@/db/initDrizzle.js"; import { createStripeCli } from "@/external/connect/createStripeCli.js"; import { resolveRedisV2 } from "@/external/redis/resolveRedisV2.js"; @@ -29,11 +30,13 @@ export const createSubOrgTestContext = async ({ testSecretKey, configOverrides, taxRegistrations, + setupDefaultFeatures, }: { subOrgSlug: string; testSecretKey: string; configOverrides?: Partial; taxRegistrations?: TaxRegistrationCountry[]; + setupDefaultFeatures?: boolean; }): Promise => { const { db } = initDrizzle(); @@ -190,6 +193,14 @@ export const createSubOrgTestContext = async ({ } } + if (setupDefaultFeatures) { + await FeatureService.insert({ + db, + data: Object.values(getFeatures({ orgId: subOrg.id })), + logger, + }); + } + // 5. Sub-org features. const features = await FeatureService.list({ db, diff --git a/server/tests/utils/testInitUtils/createTestContext.ts b/server/tests/utils/testInitUtils/createTestContext.ts index 3acbf50be..6741ad001 100644 --- a/server/tests/utils/testInitUtils/createTestContext.ts +++ b/server/tests/utils/testInitUtils/createTestContext.ts @@ -111,6 +111,19 @@ const lazyDefaultCtx = new Proxy({} as TestContext, { } return Reflect.get(ctx, prop); }, + set(_target, prop, value, _receiver) { + const ctx = (globalThis as { __autumnTestContext?: TestContext | null }) + .__autumnTestContext; + if (ctx == null) { + throw new Error( + `Default TestContext is not initialized. The integration test ` + + `preload (server/tests/setup-integration-tests.ts) did not ` + + `populate globalThis.__autumnTestContext before "${String(prop)}" ` + + `was assigned.`, + ); + } + return Reflect.set(ctx, prop, value); + }, has(_target, prop) { const ctx = (globalThis as { __autumnTestContext?: TestContext | null }) .__autumnTestContext; @@ -125,7 +138,15 @@ const lazyDefaultCtx = new Proxy({} as TestContext, { const ctx = (globalThis as { __autumnTestContext?: TestContext | null }) .__autumnTestContext; if (ctx == null) return undefined; - return Reflect.getOwnPropertyDescriptor(ctx, prop); + const descriptor = Reflect.getOwnPropertyDescriptor(ctx, prop); + if (!descriptor) return undefined; + + return { + configurable: true, + enumerable: descriptor.enumerable, + writable: true, + value: Reflect.get(ctx, prop), + }; }, }); diff --git a/server/tests/utils/testInitUtils/initScenario.ts b/server/tests/utils/testInitUtils/initScenario.ts index 36db3e63c..bbc6821e4 100644 --- a/server/tests/utils/testInitUtils/initScenario.ts +++ b/server/tests/utils/testInitUtils/initScenario.ts @@ -199,6 +199,7 @@ type PlatformCreateConfig = { userEmail?: string; configOverrides?: Partial; taxRegistrations?: TaxRegistrationCountry[]; + setupDefaultFeatures?: boolean; }; type ScenarioConfig = { @@ -789,6 +790,7 @@ const createAndRedeemReferralCode = ({ * @param userEmail - Owner email; defaults to "platform-tests@autumn.test". * @param configOverrides - Merged into the sub-org's config jsonb. * @param taxRegistrations - Countries to register Stripe Tax for. + * @param setupDefaultFeatures - Inserts standard test features on the sub-org. * * @example s.platform.create({ configOverrides: { automatic_tax: true }, taxRegistrations: ["AU"] }) */ @@ -965,6 +967,7 @@ export async function initScenario({ testSecretKey: response.test_secret_key, configOverrides: config.platformConfig.configOverrides, taxRegistrations: config.platformConfig.taxRegistrations, + setupDefaultFeatures: config.platformConfig.setupDefaultFeatures, }); console.log(