feat: 🎸 rename block_shared_pool to disable_pooled_balance

This commit is contained in:
amianthus
2026-04-20 14:42:47 +01:00
parent fb7adaf44c
commit 0a1bb92afd
10 changed files with 203 additions and 230 deletions

View File

@@ -63,7 +63,7 @@ export const prepareFeatureDeduction = ({
customerEntitlementFilters,
});
if (fullCustomer.entity?.id && fullCustomer.config?.block_shared_pool) {
if (fullCustomer.entity?.id && fullCustomer.config?.disable_pooled_balance) {
cusEnts = cusEnts.filter((ce) => isEntityCusEnt({ cusEnt: ce }));
}

View File

@@ -152,8 +152,8 @@ export const updateCustomer = async ({
? {
config: {
...(originalCustomer.config ?? {}),
...(config.block_shared_pool !== undefined && {
block_shared_pool: config.block_shared_pool,
...(config.disable_pooled_balance !== undefined && {
disable_pooled_balance: config.disable_pooled_balance,
}),
},
}

View File

@@ -86,7 +86,7 @@ export const getApiCustomerBase = async ({
overage_allowed: fullCus.overage_allowed ?? undefined,
},
config: {
block_shared_pool: fullCus.config?.block_shared_pool,
disable_pooled_balance: fullCus.config?.disable_pooled_balance,
},
invoices:

View File

@@ -9,9 +9,9 @@ import chalk from "chalk";
import { setCustomerOverageAllowed } from "../../../integration/balances/utils/overage-allowed-utils/customerOverageAllowedUtils.js";
/**
* track-entity-balances7: block_shared_pool
* track-entity-balances7: disable_pooled_balance
*
* When customer.config.block_shared_pool is true and a deduction is scoped
* When customer.config.disable_pooled_balance is true and a deduction is scoped
* to an entity, the entity must NOT fall back into the shared customer pool.
*
* Three deterministic branches:
@@ -54,7 +54,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-reject: entity ove
s.deleteCustomer({ customerId }),
s.customer({
testClock: false,
data: { config: { block_shared_pool: true } },
data: { config: { disable_pooled_balance: true } },
}),
s.products({ list: [freeProd] }),
s.entities({ count: 1, featureId: TestFeature.Users }),
@@ -111,7 +111,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-cap: entity caps a
s.deleteCustomer({ customerId }),
s.customer({
testClock: false,
data: { config: { block_shared_pool: true } },
data: { config: { disable_pooled_balance: true } },
}),
s.products({ list: [freeProd] }),
s.entities({ count: 1, featureId: TestFeature.Users }),
@@ -169,7 +169,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-overage: entity go
s.deleteCustomer({ customerId }),
s.customer({
testClock: false,
data: { config: { block_shared_pool: true } },
data: { config: { disable_pooled_balance: true } },
}),
s.products({ list: [freeProd] }),
s.entities({ count: 1, featureId: TestFeature.Users }),

View File

@@ -5,256 +5,232 @@ import chalk from "chalk";
import { CusService } from "@/internal/customers/CusService.js";
// ═══════════════════════════════════════════════════════════════════════════════
// CUSTOMER CONFIG — block_shared_pool
// CUSTOMER CONFIG — disable_pooled_balance
// Mirrors the billing_controls test style: cached API read, uncached API read,
// and a direct DB read to prove the field round-trips through every layer.
// ═══════════════════════════════════════════════════════════════════════════════
test.concurrent(
`${chalk.yellowBright("customer config: create customer without config leaves block_shared_pool undefined")}`,
async () => {
const customerId = "customer-config-default";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: create customer without config leaves disable_pooled_balance undefined")}`, async () => {
const customerId = "customer-config-default";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Default",
email: `${customerId}@example.com`,
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Default",
email: `${customerId}@example.com`,
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config).toBeDefined();
expect(cached.config?.block_shared_pool).toBeUndefined();
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config).toBeDefined();
expect(cached.config?.disable_pooled_balance).toBeUndefined();
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBeUndefined();
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBeUndefined();
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
// DB row should be null/undefined when no config was provided.
expect(fromDb.config?.block_shared_pool).toBeUndefined();
},
);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
// DB row should be null/undefined when no config was provided.
expect(fromDb.config?.disable_pooled_balance).toBeUndefined();
});
test.concurrent(
`${chalk.yellowBright("customer config: create customer with block_shared_pool=true")}`,
async () => {
const customerId = "customer-config-create-true";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: create customer with disable_pooled_balance=true")}`, async () => {
const customerId = "customer-config-create-true";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Create True",
email: `${customerId}@example.com`,
config: { block_shared_pool: true },
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Create True",
email: `${customerId}@example.com`,
config: { disable_pooled_balance: true },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.block_shared_pool).toBe(true);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.disable_pooled_balance).toBe(true);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(true);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(true);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.block_shared_pool).toBe(true);
},
);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.disable_pooled_balance).toBe(true);
});
test.concurrent(
`${chalk.yellowBright("customer config: create customer with block_shared_pool=false")}`,
async () => {
const customerId = "customer-config-create-false";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: create customer with disable_pooled_balance=false")}`, async () => {
const customerId = "customer-config-create-false";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Create False",
email: `${customerId}@example.com`,
config: { block_shared_pool: false },
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Create False",
email: `${customerId}@example.com`,
config: { disable_pooled_balance: false },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.block_shared_pool).toBe(false);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.disable_pooled_balance).toBe(false);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(false);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(false);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.block_shared_pool).toBe(false);
},
);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.disable_pooled_balance).toBe(false);
});
test.concurrent(
`${chalk.yellowBright("customer config: update block_shared_pool from unset to true")}`,
async () => {
const customerId = "customer-config-update-true";
const { autumnV2_1, ctx } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: update disable_pooled_balance from unset to true")}`, async () => {
const customerId = "customer-config-update-true";
const { autumnV2_1, ctx } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
const before = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(before.config?.block_shared_pool).toBeUndefined();
const before = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(before.config?.disable_pooled_balance).toBeUndefined();
await autumnV2_1.customers.update(customerId, {
config: { block_shared_pool: true },
});
await autumnV2_1.customers.update(customerId, {
config: { disable_pooled_balance: true },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.block_shared_pool).toBe(true);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.disable_pooled_balance).toBe(true);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(true);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(true);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.block_shared_pool).toBe(true);
},
);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.config?.disable_pooled_balance).toBe(true);
});
test.concurrent(
`${chalk.yellowBright("customer config: update block_shared_pool from true to false")}`,
async () => {
const customerId = "customer-config-update-false";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: update disable_pooled_balance from true to false")}`, async () => {
const customerId = "customer-config-update-false";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Flip Off",
email: `${customerId}@example.com`,
config: { block_shared_pool: true },
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Flip Off",
email: `${customerId}@example.com`,
config: { disable_pooled_balance: true },
});
const before = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(before.config?.block_shared_pool).toBe(true);
const before = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(before.config?.disable_pooled_balance).toBe(true);
await autumnV2_1.customers.update(customerId, {
config: { block_shared_pool: false },
});
await autumnV2_1.customers.update(customerId, {
config: { disable_pooled_balance: false },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.block_shared_pool).toBe(false);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.disable_pooled_balance).toBe(false);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(false);
},
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(false);
});
test.concurrent(
`${chalk.yellowBright("customer config: partial update leaves other fields untouched")}`,
async () => {
// Regression guard: mirrors the billing_controls pattern — updating config
// shouldn't clobber name/email/send_email_receipts.
const customerId = "customer-config-partial";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: partial update leaves other fields untouched")}`, async () => {
// Regression guard: mirrors the billing_controls pattern — updating config
// shouldn't clobber name/email/send_email_receipts.
const customerId = "customer-config-partial";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Original Name",
email: `${customerId}@example.com`,
send_email_receipts: true,
});
await autumnV2_1.customers.create({
id: customerId,
name: "Original Name",
email: `${customerId}@example.com`,
send_email_receipts: true,
});
await autumnV2_1.customers.update(customerId, {
config: { block_shared_pool: true },
});
await autumnV2_1.customers.update(customerId, {
config: { disable_pooled_balance: true },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.name).toBe("Original Name");
expect(cached.email).toBe(`${customerId}@example.com`);
expect(cached.send_email_receipts).toBe(true);
expect(cached.config?.block_shared_pool).toBe(true);
},
);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.name).toBe("Original Name");
expect(cached.email).toBe(`${customerId}@example.com`);
expect(cached.send_email_receipts).toBe(true);
expect(cached.config?.disable_pooled_balance).toBe(true);
});
test.concurrent(
`${chalk.yellowBright("customer config: omitting config in update does not reset it")}`,
async () => {
const customerId = "customer-config-omit";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: omitting config in update does not reset it")}`, async () => {
const customerId = "customer-config-omit";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Persist",
email: `${customerId}@example.com`,
config: { block_shared_pool: true },
});
await autumnV2_1.customers.create({
id: customerId,
name: "Config Persist",
email: `${customerId}@example.com`,
config: { disable_pooled_balance: true },
});
// Update something unrelated — config should survive.
await autumnV2_1.customers.update(customerId, {
name: "Config Persist Renamed",
});
// Update something unrelated — config should survive.
await autumnV2_1.customers.update(customerId, {
name: "Config Persist Renamed",
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.name).toBe("Config Persist Renamed");
expect(cached.config?.block_shared_pool).toBe(true);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.name).toBe("Config Persist Renamed");
expect(cached.config?.disable_pooled_balance).toBe(true);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(true);
},
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(true);
});
test.concurrent(
`${chalk.yellowBright("customer config: updating billing_controls alongside config does not clobber either")}`,
async () => {
// Cross-field regression: config and billing_controls are both partial
// updates on the same row — updating one must not reset the other.
const customerId = "customer-config-mixed";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
test.concurrent(`${chalk.yellowBright("customer config: updating billing_controls alongside config does not clobber either")}`, async () => {
// Cross-field regression: config and billing_controls are both partial
// updates on the same row — updating one must not reset the other.
const customerId = "customer-config-mixed";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Mixed",
email: `${customerId}@example.com`,
config: { block_shared_pool: true },
});
await autumnV2_1.customers.create({
id: customerId,
name: "Mixed",
email: `${customerId}@example.com`,
config: { disable_pooled_balance: true },
});
await autumnV2_1.customers.update(customerId, {
billing_controls: {
spend_limits: [],
},
});
await autumnV2_1.customers.update(customerId, {
billing_controls: {
spend_limits: [],
},
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.block_shared_pool).toBe(true);
expect(cached.billing_controls?.spend_limits).toEqual([]);
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.config?.disable_pooled_balance).toBe(true);
expect(cached.billing_controls?.spend_limits).toEqual([]);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.block_shared_pool).toBe(true);
expect(uncached.billing_controls?.spend_limits).toEqual([]);
},
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.config?.disable_pooled_balance).toBe(true);
expect(uncached.billing_controls?.spend_limits).toEqual([]);
});

View File

@@ -56,7 +56,7 @@ export const CustomerDataSchema = z
config: z
.object({
block_shared_pool: z.boolean().optional().meta({
disable_pooled_balance: z.boolean().optional().meta({
description:
"Whether to block this customer from the shared feature pool.",
}),

View File

@@ -76,7 +76,7 @@ export const API_CUSTOMER_V5_EXAMPLE = {
},
},
config: {
block_shared_pool: false,
disable_pooled_balance: false,
},
};
@@ -99,12 +99,9 @@ export const BaseApiCustomerV5Schema = BaseApiCustomerSchema.extend({
}),
config: z
.object({
block_shared_pool: z
.boolean()
.optional()
.meta({
description: "Whether to block the shared pool for the customer.",
}),
disable_pooled_balance: z.boolean().optional().meta({
description: "Whether to block the shared pool for the customer.",
}),
})
.optional()
.meta({

View File

@@ -29,11 +29,11 @@ export const getApiBalances = async ({
entity: fullCus.entity,
});
// When block_shared_pool is enabled and we're scoped to an entity, drop
// When disable_pooled_balance is enabled and we're scoped to an entity, drop
// customer-level (shared pool) cusEnts so the returned balances reflect
// only the entity's own pool. Matches the filter in prepareFeatureDeduction
// so the deduction path and reporting stay consistent.
if (fullCus.entity?.id && fullCus.config?.block_shared_pool) {
if (fullCus.entity?.id && fullCus.config?.disable_pooled_balance) {
allCusEnts = allCusEnts.filter((ce) => isEntityCusEnt({ cusEnt: ce }));
}

View File

@@ -29,7 +29,7 @@ export const CustomerSchema = z.object({
overage_allowed: z.array(DbOverageAllowedSchema).nullish(),
config: z
.object({
block_shared_pool: z.boolean().optional(),
disable_pooled_balance: z.boolean().optional(),
})
.nullish(),
});

View File

@@ -21,7 +21,7 @@ import type {
} from "./billingControls/customerBillingControls.js";
export type CustomerConfig = {
block_shared_pool?: boolean;
disable_pooled_balance?: boolean;
};
export type CustomerProcessor = {