test: improve billing.ts and demo.ts branch coverage
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 21m45s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 21m45s
This commit is contained in:
parent
3aae96fd8a
commit
1363c61e39
2 changed files with 553 additions and 0 deletions
|
|
@ -257,4 +257,334 @@ describe("Billing Branch Coverage", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Success route - customerId branch (line 163)", () => {
|
||||
it("should return 400 when session.customer is null (not a string)", async () => {
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_null_customer",
|
||||
customer: null, // Explicitly null, not falsy string
|
||||
customer_details: { email: "test@example.com" },
|
||||
});
|
||||
|
||||
const res = await request(app).get("/v1/billing/success?session_id=cs_null_customer");
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("No customer found");
|
||||
});
|
||||
|
||||
it("should return 400 when customer is empty string", async () => {
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_empty_customer",
|
||||
customer: "", // Empty string is falsy
|
||||
customer_details: { email: "test@example.com" },
|
||||
});
|
||||
|
||||
const res = await request(app).get("/v1/billing/success?session_id=cs_empty_customer");
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("No customer found");
|
||||
});
|
||||
|
||||
it("should return 400 when customer is undefined", async () => {
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_undef_customer",
|
||||
customer: undefined,
|
||||
customer_details: { email: "test@example.com" },
|
||||
});
|
||||
|
||||
const res = await request(app).get("/v1/billing/success?session_id=cs_undef_customer");
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("No customer found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Webhook checkout.session.completed - hasDocfastProduct branch (line 223)", () => {
|
||||
it("should skip webhook event when line_items is undefined", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "checkout.session.completed",
|
||||
data: {
|
||||
object: {
|
||||
id: "cs_no_items",
|
||||
customer: "cus_no_items",
|
||||
customer_details: { email: "test@example.com" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Mock: line_items is undefined
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_no_items",
|
||||
line_items: undefined,
|
||||
});
|
||||
|
||||
const { createProKey } = await import("../services/keys.js");
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "checkout.session.completed" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(createProKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip webhook event when line_items.data is empty", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "checkout.session.completed",
|
||||
data: {
|
||||
object: {
|
||||
id: "cs_empty_items",
|
||||
customer: "cus_empty_items",
|
||||
customer_details: { email: "test@example.com" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_empty_items",
|
||||
line_items: { data: [] }, // Empty array - no items
|
||||
});
|
||||
|
||||
const { createProKey } = await import("../services/keys.js");
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "checkout.session.completed" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(createProKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip webhook event when price is null", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "checkout.session.completed",
|
||||
data: {
|
||||
object: {
|
||||
id: "cs_null_price",
|
||||
customer: "cus_null_price",
|
||||
customer_details: { email: "test@example.com" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_null_price",
|
||||
line_items: {
|
||||
data: [{ price: null }], // Null price
|
||||
},
|
||||
});
|
||||
|
||||
const { createProKey } = await import("../services/keys.js");
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "checkout.session.completed" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(createProKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip webhook event when product is null", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "checkout.session.completed",
|
||||
data: {
|
||||
object: {
|
||||
id: "cs_null_product",
|
||||
customer: "cus_null_product",
|
||||
customer_details: { email: "test@example.com" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockStripe.checkout.sessions.retrieve.mockResolvedValue({
|
||||
id: "cs_null_product",
|
||||
line_items: {
|
||||
data: [{ price: { product: null } }], // Null product
|
||||
},
|
||||
});
|
||||
|
||||
const { createProKey } = await import("../services/keys.js");
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "checkout.session.completed" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(createProKey).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Webhook customer.updated event (line 284-303)", () => {
|
||||
it("should sync email when both customerId and newEmail exist", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "customer.updated",
|
||||
data: {
|
||||
object: {
|
||||
id: "cus_email_update",
|
||||
email: "newemail@example.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { updateEmailByCustomer } = await import("../services/keys.js");
|
||||
vi.mocked(updateEmailByCustomer).mockResolvedValue(true);
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "customer.updated" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(updateEmailByCustomer).toHaveBeenCalledWith("cus_email_update", "newemail@example.com");
|
||||
});
|
||||
|
||||
it("should not sync email when customerId is missing", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "customer.updated",
|
||||
data: {
|
||||
object: {
|
||||
id: undefined, // Missing customerId
|
||||
email: "newemail@example.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { updateEmailByCustomer } = await import("../services/keys.js");
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "customer.updated" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(updateEmailByCustomer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not sync email when email is missing", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "customer.updated",
|
||||
data: {
|
||||
object: {
|
||||
id: "cus_no_email",
|
||||
email: null, // Missing email
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { updateEmailByCustomer } = await import("../services/keys.js");
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "customer.updated" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(updateEmailByCustomer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not sync email when email is undefined", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "customer.updated",
|
||||
data: {
|
||||
object: {
|
||||
id: "cus_no_email_2",
|
||||
email: undefined, // Undefined email
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { updateEmailByCustomer } = await import("../services/keys.js");
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "customer.updated" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(updateEmailByCustomer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log when email update returns false", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "customer.updated",
|
||||
data: {
|
||||
object: {
|
||||
id: "cus_no_update",
|
||||
email: "newemail@example.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { updateEmailByCustomer } = await import("../services/keys.js");
|
||||
vi.mocked(updateEmailByCustomer).mockResolvedValue(false); // Update returns false
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "customer.updated" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(updateEmailByCustomer).toHaveBeenCalledWith("cus_no_update", "newemail@example.com");
|
||||
// The if (updated) branch should not be executed when false
|
||||
});
|
||||
});
|
||||
|
||||
describe("Webhook default case", () => {
|
||||
it("should handle unknown webhook event type", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "unknown.event.type",
|
||||
data: { object: {} },
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "unknown.event.type" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.received).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle payment_intent.succeeded webhook event", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "payment_intent.succeeded",
|
||||
data: { object: {} },
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "payment_intent.succeeded" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.received).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle invoice.payment_succeeded webhook event", async () => {
|
||||
mockStripe.webhooks.constructEvent.mockReturnValue({
|
||||
type: "invoice.payment_succeeded",
|
||||
data: { object: {} },
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post("/v1/billing/webhook")
|
||||
.set("content-type", "application/json")
|
||||
.set("stripe-signature", "valid_sig")
|
||||
.send(JSON.stringify({ type: "invoice.payment_succeeded" }));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.received).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue