fix: 🐛 clean up

This commit is contained in:
amianthus
2026-04-28 18:56:44 +01:00
parent ff3825a432
commit eb2c063a6f
3 changed files with 152 additions and 9 deletions

View File

@@ -0,0 +1,138 @@
/**
* Unit tests for filterCustomerProductsByProcessorType.
*
* Critical invariant: an unset `processor` field on a cus product MUST be
* treated as Stripe. Legacy Stripe-managed cus products do not tag the
* processor field; only RevenueCat-managed products explicitly set it.
*/
import { describe, expect, test } from "bun:test";
import {
filterCustomerProductsByProcessorType,
type FullCusProduct,
ProcessorType,
} from "@autumn/shared";
import chalk from "chalk";
const baseCusProduct = (id: string): FullCusProduct =>
({
id,
product: { name: id } as FullCusProduct["product"],
}) as FullCusProduct;
describe(
chalk.yellowBright("filterCustomerProductsByProcessorType"),
() => {
test("includes cus product with unset processor when filtering for Stripe", () => {
const cp = baseCusProduct("legacy_stripe");
// processor is undefined — legacy data shape
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.Stripe,
});
expect(result).toHaveLength(1);
expect(result[0].id).toBe("legacy_stripe");
});
test("includes cus product with null processor when filtering for Stripe", () => {
const cp = {
...baseCusProduct("null_stripe"),
processor: null,
} as unknown as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.Stripe,
});
expect(result).toHaveLength(1);
});
test("includes cus product with explicit Stripe processor when filtering for Stripe", () => {
const cp = {
...baseCusProduct("explicit_stripe"),
processor: { type: ProcessorType.Stripe },
} as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.Stripe,
});
expect(result).toHaveLength(1);
});
test("excludes cus product with unset processor when filtering for RevenueCat", () => {
const cp = baseCusProduct("legacy_stripe_excluded");
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.RevenueCat,
});
expect(result).toHaveLength(0);
});
test("includes cus product with explicit RevenueCat processor when filtering for RevenueCat", () => {
const cp = {
...baseCusProduct("rc"),
processor: { type: ProcessorType.RevenueCat },
} as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.RevenueCat,
});
expect(result).toHaveLength(1);
});
test("excludes RevenueCat cus product when filtering for Stripe", () => {
const cp = {
...baseCusProduct("rc"),
processor: { type: ProcessorType.RevenueCat },
} as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [cp],
processorType: ProcessorType.Stripe,
});
expect(result).toHaveLength(0);
});
test("mixed list: filtering for Stripe keeps unset + explicit Stripe, drops RevenueCat", () => {
const legacyStripe = baseCusProduct("legacy_stripe");
const explicitStripe = {
...baseCusProduct("explicit_stripe"),
processor: { type: ProcessorType.Stripe },
} as FullCusProduct;
const rc = {
...baseCusProduct("rc"),
processor: { type: ProcessorType.RevenueCat },
} as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [legacyStripe, explicitStripe, rc],
processorType: ProcessorType.Stripe,
});
expect(result.map((cp) => cp.id).sort()).toEqual(
["explicit_stripe", "legacy_stripe"].sort(),
);
});
test("mixed list: filtering for RevenueCat keeps only explicit RC", () => {
const legacyStripe = baseCusProduct("legacy_stripe");
const rc = {
...baseCusProduct("rc"),
processor: { type: ProcessorType.RevenueCat },
} as FullCusProduct;
const result = filterCustomerProductsByProcessorType({
customerProducts: [legacyStripe, rc],
processorType: ProcessorType.RevenueCat,
});
expect(result).toHaveLength(1);
expect(result[0].id).toBe("rc");
});
test("empty list returns empty list", () => {
expect(
filterCustomerProductsByProcessorType({
customerProducts: [],
processorType: ProcessorType.Stripe,
}),
).toEqual([]);
});
},
);

View File

@@ -1247,7 +1247,9 @@ describe(
options: stripeAddOn.allOptions,
status: CusProductStatus.Active,
subscriptionIds: [],
// processor defaults to Stripe via cusProductToProcessorType
// IMPORTANT: processor is intentionally unset here — legacy Stripe-managed
// cus products have no `processor` field. The filter must treat unset as
// Stripe (so this product's items get included). RevenueCat is always tagged.
});
const ctx = contexts.create({ features: [] });

View File

@@ -1,13 +1,14 @@
import type { FullCusProduct } from "@models/cusProductModels/cusProductModels";
import { ProcessorType } from "@models/genModels/genEnums";
import { cusProductToProcessorType } from "../convertCusProduct.js";
/**
* Filter customer products by processor type.
*
* `cusProductToProcessorType` treats an unset `processor` as Stripe (default),
* so passing `ProcessorType.Stripe` keeps both legacy unset rows and explicit
* Stripe-tagged rows.
* IMPORTANT: a customer product with no `processor` set (or `processor.type` unset)
* is treated as Stripe. Historically, Stripe-managed cus products were created
* without explicitly tagging the processor field, so a null/undefined processor
* defaults to Stripe. RevenueCat-managed products always have
* `processor.type === ProcessorType.RevenueCat` explicitly set.
*
* @param customerProducts - The customer products to filter
* @param processorType - The processor type to keep (e.g. `ProcessorType.Stripe`)
@@ -20,8 +21,10 @@ export const filterCustomerProductsByProcessorType = ({
customerProducts: FullCusProduct[];
processorType: ProcessorType;
}): FullCusProduct[] => {
return customerProducts.filter(
(customerProduct) =>
cusProductToProcessorType(customerProduct) === processorType,
);
return customerProducts.filter((customerProduct) => {
// Default unset processor to Stripe — RevenueCat is always explicitly tagged.
const cusProductProcessorType =
customerProduct.processor?.type ?? ProcessorType.Stripe;
return cusProductProcessorType === processorType;
});
};