112 lines
3.7 KiB
JavaScript
112 lines
3.7 KiB
JavaScript
import StripeService from "@/services/system/StripeService.js";
|
|
|
|
// Mocks
|
|
jest.mock("@/db/models/index.js", () => ({
|
|
Org: { findOneAndUpdate: jest.fn() },
|
|
Monitor: { countDocuments: jest.fn(), find: jest.fn(), deleteMany: jest.fn() },
|
|
StatusPage: { countDocuments: jest.fn(), find: jest.fn(), deleteMany: jest.fn() },
|
|
NotificationChannel: { countDocuments: jest.fn(), find: jest.fn(), deleteMany: jest.fn() },
|
|
}));
|
|
import { Org, Monitor, StatusPage, NotificationChannel } from "@/db/models/index.js";
|
|
|
|
jest.mock("@/config/index.js", () => ({
|
|
config: {
|
|
STRIPE_SECRET: "sk_test_mock",
|
|
STRIPE_WEBHOOK_SECRET: "whsec_mock",
|
|
},
|
|
}));
|
|
|
|
// Mock Stripe constructor and webhooks
|
|
jest.mock("stripe", () => {
|
|
const mockConstructEvent = jest.fn();
|
|
const mockStripe = function () {
|
|
return {
|
|
webhooks: { constructEvent: mockConstructEvent },
|
|
};
|
|
};
|
|
mockStripe.__constructEvent = mockConstructEvent;
|
|
return { __esModule: true, default: mockStripe };
|
|
});
|
|
import Stripe from "stripe";
|
|
|
|
const buildSub = (overrides = {}) => ({
|
|
id: "sub_1",
|
|
metadata: { orgId: "org1" },
|
|
status: "active",
|
|
items: { data: [{ price: { id: "price_1", lookup_key: "pro" }, current_period_start: 1, current_period_end: 2 }] },
|
|
...overrides,
|
|
});
|
|
|
|
describe("StripeService", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it("returns null when constructEvent fails", async () => {
|
|
(Stripe.__constructEvent).mockImplementation(() => {
|
|
throw new Error("bad sig");
|
|
});
|
|
const svc = new StripeService();
|
|
const res = await svc.webhook("evt", "sig");
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it("handles subscription created/updated and updates org + prunes resources", async () => {
|
|
(Stripe.__constructEvent).mockReturnValue({
|
|
type: "customer.subscription.created",
|
|
data: { object: buildSub() },
|
|
});
|
|
|
|
Monitor.countDocuments.mockResolvedValue(30);
|
|
Monitor.find.mockReturnValue({ sort: () => ({ limit: () => ({ select: () => Promise.resolve([{ _id: "m1" }]) }) }) });
|
|
Monitor.deleteMany.mockResolvedValue({});
|
|
NotificationChannel.countDocuments.mockResolvedValue(0);
|
|
StatusPage.countDocuments.mockResolvedValue(0);
|
|
|
|
const svc = new StripeService();
|
|
const res = await svc.webhook("evt", "sig");
|
|
expect(Org.findOneAndUpdate).toHaveBeenCalledWith(
|
|
{ _id: "org1" },
|
|
expect.objectContaining({ $set: expect.objectContaining({ planKey: "pro" }) }),
|
|
{ new: true }
|
|
);
|
|
expect(Monitor.deleteMany).toHaveBeenCalled();
|
|
expect(res).toMatchObject({ status: "active" });
|
|
});
|
|
|
|
it("handles subscription deleted and resets org to free", async () => {
|
|
(Stripe.__constructEvent).mockReturnValue({
|
|
type: "customer.subscription.deleted",
|
|
data: { object: buildSub() },
|
|
});
|
|
const svc = new StripeService();
|
|
const res = await svc.webhook("evt", "sig");
|
|
expect(Org.findOneAndUpdate).toHaveBeenCalledWith(
|
|
{ _id: "org1" },
|
|
expect.objectContaining({ $set: expect.objectContaining({ planKey: "free" }), $unset: expect.any(Object) }),
|
|
{ new: true }
|
|
);
|
|
expect(res).toMatchObject({ status: "active" });
|
|
});
|
|
|
|
it("returns subscription for updated event", async () => {
|
|
(Stripe.__constructEvent).mockReturnValue({
|
|
type: "customer.subscription.updated",
|
|
data: { object: buildSub({ status: "past_due" }) },
|
|
});
|
|
const svc = new StripeService();
|
|
const res = await svc.webhook("evt", "sig");
|
|
expect(res).toMatchObject({ status: "past_due" });
|
|
});
|
|
|
|
it("returns null for other events", async () => {
|
|
(Stripe.__constructEvent).mockReturnValue({
|
|
type: "invoice.payment_succeeded",
|
|
data: { object: {} },
|
|
});
|
|
const svc = new StripeService();
|
|
const res = await svc.webhook("evt", "sig");
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|