From ab601c0544860cb6d8a844cdcf50c39b7e07c3e3 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Wed, 10 Jun 2026 13:21:32 +0100 Subject: [PATCH] chore: cubic comments --- .../mergeEntityAndCustomerSubjectRows.ts | 8 +++++- ...e-entity-and-customer-subject-rows.test.ts | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts b/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts index 741fca752..0249c175f 100644 --- a/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts +++ b/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts @@ -39,8 +39,14 @@ export const mergeEntityAndCustomerSubjectRows = ({ }): SubjectQueryRow => { if (!customerRow) return entityRow; + // The entityScopedOnly query matches on internal_entity_id alone (adding the + // customer predicate degrades its plan), so enforce the customer match here. + // Dependent rows of any dropped product are filtered transitively below. const customerProducts = [ - ...entityRow.customer_products, + ...entityRow.customer_products.filter( + (product) => + product.internal_customer_id === entityRow.customer.internal_id, + ), ...customerRow.customer_products, ].slice(0, CUSTOMER_PRODUCT_LIMIT); diff --git a/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts b/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts index c4003743d..6d56d9eb9 100644 --- a/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts +++ b/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts @@ -14,17 +14,20 @@ import { mergeEntityAndCustomerSubjectRows } from "@/internal/customers/repos/ge const createCustomerProduct = ({ id, internalProductId = `prod_internal_${id}`, + internalCustomerId = "cus_internal_1", freeTrialId = null, subscriptionIds = [], }: { id: string; internalProductId?: string; + internalCustomerId?: string; freeTrialId?: string | null; subscriptionIds?: string[]; }) => ({ id, internal_product_id: internalProductId, + internal_customer_id: internalCustomerId, free_trial_id: freeTrialId, subscription_ids: subscriptionIds, }) as DbCustomerProduct; @@ -90,6 +93,28 @@ describe("mergeEntityAndCustomerSubjectRows", () => { expect(merged).toBe(entityRow); }); + test("drops entity-scoped products belonging to a different customer", () => { + const entityRow = createRow({ + customer_products: [ + createCustomerProduct({ id: "cp_ours" }), + createCustomerProduct({ + id: "cp_other_customer", + internalCustomerId: "cus_internal_other", + }), + ], + }); + const customerRow = createRow(); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.customer_products.map((product) => product.id)).toEqual([ + "cp_ours", + ]); + }); + test("orders entity-scoped rows before customer-level rows", () => { const entityRow = createRow({ customer_products: [