security audit fix start
This commit is contained in:
149
web/src/server/api/schemas/webhook.test.ts
Normal file
149
web/src/server/api/schemas/webhook.test.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { safeParse } from "valibot";
|
||||
import {
|
||||
CheckoutSessionSchema,
|
||||
SubscriptionSchema,
|
||||
InvoiceSchema,
|
||||
} from "./webhook";
|
||||
|
||||
describe("CheckoutSessionSchema", () => {
|
||||
it("accepts valid checkout session data", () => {
|
||||
const data = {
|
||||
id: "cs_test123",
|
||||
subscription: "sub_123",
|
||||
metadata: { userId: "user_123" },
|
||||
};
|
||||
const result = safeParse(CheckoutSessionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.output.id).toBe("cs_test123");
|
||||
expect(result.output.metadata?.userId).toBe("user_123");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts session without optional fields", () => {
|
||||
const data = { id: "cs_test123" };
|
||||
const result = safeParse(CheckoutSessionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects missing required id", () => {
|
||||
const data = { subscription: "sub_123" };
|
||||
const result = safeParse(CheckoutSessionSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects non-string id", () => {
|
||||
const data = { id: 123 };
|
||||
const result = safeParse(CheckoutSessionSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SubscriptionSchema", () => {
|
||||
it("accepts valid subscription data with integer timestamps", () => {
|
||||
const data = {
|
||||
id: "sub_123",
|
||||
status: "active",
|
||||
current_period_start: 1700000000,
|
||||
current_period_end: 1702678400,
|
||||
cancel_at_period_end: "false",
|
||||
metadata: { userId: "user_123" },
|
||||
items: {
|
||||
data: { price: { id: "price_basic" } },
|
||||
},
|
||||
};
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.output.current_period_start).toBe(1700000000);
|
||||
expect(result.output.items?.data?.price?.id).toBe("price_basic");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects non-integer timestamps", () => {
|
||||
const data = {
|
||||
id: "sub_123",
|
||||
current_period_start: "not-a-number",
|
||||
};
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults cancel_at_period_end when not provided", () => {
|
||||
const data = { id: "sub_123" };
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.output.cancel_at_period_end).toBe("false");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts string cancel_at_period_end", () => {
|
||||
const data = { id: "sub_123", cancel_at_period_end: "true" };
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects missing required id", () => {
|
||||
const data = { status: "active" };
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("handles extra unexpected fields gracefully", () => {
|
||||
const data = {
|
||||
id: "sub_123",
|
||||
status: "active",
|
||||
unknown_field: "should be ignored",
|
||||
};
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("InvoiceSchema", () => {
|
||||
it("accepts valid invoice data", () => {
|
||||
const data = { subscription: "sub_123" };
|
||||
const result = safeParse(InvoiceSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.output.subscription).toBe("sub_123");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts invoice without subscription (for partial invoices)", () => {
|
||||
const data = { id: "in_123" };
|
||||
const result = safeParse(InvoiceSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects non-string subscription", () => {
|
||||
const data = { subscription: 123 };
|
||||
const result = safeParse(InvoiceSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Webhook data validation - malformed payloads", () => {
|
||||
it("handles empty object", () => {
|
||||
const result = safeParse(SubscriptionSchema, {});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("handles completely wrong data shape", () => {
|
||||
const result = safeParse(SubscriptionSchema, "not an object");
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("handles unexpected fields without crashing", () => {
|
||||
const data = {
|
||||
id: "sub_123",
|
||||
status: "active",
|
||||
unknown_field: "should be ignored",
|
||||
another_unknown: 42,
|
||||
};
|
||||
const result = safeParse(SubscriptionSchema, data);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user