mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Fix null-unsafe payments config validation for partial overrides (#1363)
## Summary - Make the `branchPaymentsSchema` custom validator tolerant of partial override objects - Avoid crashing when `payments.products` or `payments.productLines` are absent during validation - Add regression tests for partial configs plus the existing missing-line and customer-type mismatch cases ## Testing - Added Vitest coverage for partial payments configs and validation failures - Lint passed for the touched schema files - Typecheck passed for `packages/stack-shared` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved validation robustness with stricter type-safety checks for payment-related data configurations. * Enhanced error messages for clearer feedback on validation failures. * **Tests** * Added comprehensive test coverage for edge cases including missing configurations and type mismatches. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
e50358710a
commit
e0c1cc5376
@ -181,18 +181,25 @@ export const branchPaymentsSchema = yupObject({
|
||||
'Product customer type must match its product line customer type',
|
||||
function(this: yup.TestContext<yup.AnyObject>, value) {
|
||||
if (!value) return true;
|
||||
for (const [productId, product] of Object.entries(value.products)) {
|
||||
if (!product.productLineId) continue;
|
||||
const productLine = getOrUndefined(value.productLines, product.productLineId);
|
||||
const products = value.products;
|
||||
if (!isObjectLike(products)) return true;
|
||||
|
||||
const productLines = value.productLines;
|
||||
for (const [productId, product] of Object.entries(products)) {
|
||||
if (!isObjectLike(product)) continue;
|
||||
const productLineId = product.productLineId;
|
||||
if (typeof productLineId !== "string" || productLineId.length === 0) continue;
|
||||
const productLine = isObjectLike(productLines) ? getOrUndefined(productLines, productLineId) : undefined;
|
||||
if (productLine === undefined) {
|
||||
return this.createError({
|
||||
message: `Product "${productId}" specifies product line ID "${product.productLineId}", but that product line does not exist`,
|
||||
message: `Product "${productId}" specifies product line ID "${productLineId}", but that product line does not exist`,
|
||||
path: `${this.path}.products.${productId}.productLineId`,
|
||||
});
|
||||
}
|
||||
if (!isObjectLike(productLine)) continue;
|
||||
if (product.customerType !== productLine.customerType) {
|
||||
return this.createError({
|
||||
message: `Product "${productId}" has customer type "${product.customerType}" but its product line "${product.productLineId}" has customer type "${productLine.customerType}"`,
|
||||
message: `Product "${productId}" has customer type "${product.customerType}" but its product line "${productLineId}" has customer type "${productLine.customerType}"`,
|
||||
path: `${this.path}.products.${productId}.customerType`,
|
||||
});
|
||||
}
|
||||
@ -200,6 +207,95 @@ export const branchPaymentsSchema = yupObject({
|
||||
return true;
|
||||
}
|
||||
);
|
||||
import.meta.vitest?.test("branchPaymentsSchema accepts partial payments config without products", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
blockNewPurchases: true,
|
||||
}, { abortEarly: false })).resolves.toMatchObject({
|
||||
blockNewPurchases: true,
|
||||
});
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema accepts product lines without products", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
productLines: {
|
||||
pro: {
|
||||
displayName: "Pro",
|
||||
customerType: "user",
|
||||
},
|
||||
},
|
||||
}, { abortEarly: false })).resolves.toMatchObject({
|
||||
productLines: {
|
||||
pro: {
|
||||
displayName: "Pro",
|
||||
customerType: "user",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema rejects a product that references a missing product line", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
products: {
|
||||
pro: {
|
||||
customerType: "user",
|
||||
productLineId: "missing-line",
|
||||
prices: "include-by-default",
|
||||
},
|
||||
},
|
||||
}, { abortEarly: false })).rejects.toThrowErrorMatchingInlineSnapshot(`[ValidationError: Product "pro" specifies product line ID "missing-line", but that product line does not exist]`);
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema rejects null product entries without throwing a raw TypeError", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
products: {
|
||||
pro: null,
|
||||
},
|
||||
}, { abortEarly: false })).rejects.toThrowErrorMatchingInlineSnapshot(`[ValidationError: products cannot be null]`);
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema rejects null product line entries without throwing a raw TypeError", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
productLines: {
|
||||
teamLine: null,
|
||||
},
|
||||
products: {
|
||||
pro: {
|
||||
customerType: "user",
|
||||
productLineId: "teamLine",
|
||||
prices: "include-by-default",
|
||||
},
|
||||
},
|
||||
}, { abortEarly: false })).rejects.toThrowErrorMatchingInlineSnapshot(`[ValidationError: productLines cannot be null]`);
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema rejects a product whose customer type differs from its product line", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
productLines: {
|
||||
teamLine: {
|
||||
customerType: "team",
|
||||
},
|
||||
},
|
||||
products: {
|
||||
pro: {
|
||||
customerType: "user",
|
||||
productLineId: "teamLine",
|
||||
prices: "include-by-default",
|
||||
},
|
||||
},
|
||||
}, { abortEarly: false })).rejects.toThrowErrorMatchingInlineSnapshot(`[ValidationError: Product "pro" has customer type "user" but its product line "teamLine" has customer type "team"]`);
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("branchPaymentsSchema lets productLineId schema reject empty IDs", async ({ expect }) => {
|
||||
await expect(branchPaymentsSchema.validate({
|
||||
products: {
|
||||
pro: {
|
||||
customerType: "user",
|
||||
productLineId: "",
|
||||
prices: "include-by-default",
|
||||
},
|
||||
},
|
||||
}, { abortEarly: false })).rejects.toThrowErrorMatchingInlineSnapshot(`[ValidationError: productLineId must contain only letters, numbers, underscores, and hyphens, and not start with a hyphen]`);
|
||||
});
|
||||
|
||||
const branchDomain = yupObject({});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user