/** * Property-based tests for payment functions in lib/summit-requests.ts * Feature: paypal-payment-integration */ import { describe, it, expect, vi, beforeEach } from "vitest"; import * as fc from "fast-check"; import type { AdminRequestRow } from "@/types/admin-submission"; // --------------------------------------------------------------------------- // Mocks // --------------------------------------------------------------------------- const mockCreate = vi.fn(); const mockFindOneAndUpdate = vi.fn(); const mockFindOne = vi.fn(); vi.mock("@/lib/mongodb", () => ({ default: vi.fn().mockResolvedValue(undefined) })); vi.mock("@/models/SummitRequest", () => ({ default: { create: (...args: unknown[]) => mockCreate(...args), findOneAndUpdate: (...args: unknown[]) => mockFindOneAndUpdate(...args), findOne: (...args: unknown[]) => mockFindOne(...args), }, })); const mockRow: AdminRequestRow = { id: "V6-TEST", submittedAt: "2026-01-01", displayDate: "01 JAN 2026", fullName: "Test User", jobTitle: "Engineer", company: "Test Co", segment: "enterprise", email: "test@example.com", phone: "+84 123456789", status: "pending", notes: "", source: "registration", }; // --------------------------------------------------------------------------- // Property 4: PayPal Order ID được lưu với trạng thái payment_pending // --------------------------------------------------------------------------- describe("createRegistrationWithPayment", () => { beforeEach(() => { mockCreate.mockReset(); mockCreate.mockResolvedValue({}); }); it( "Feature: paypal-payment-integration, Property 4: Order ID lưu với payment_pending", async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 50 }), async (orderId) => { const { createRegistrationWithPayment } = await import("../summit-requests"); await createRegistrationWithPayment(mockRow, orderId); expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ paymentStatus: "payment_pending", paypalOrderId: orderId, paymentAmount: 120, paymentCurrency: "USD", }) ); } ), { numRuns: 50 } ); } ); }); // --------------------------------------------------------------------------- // Property 5: Capture thành công chuyển trạng thái sang paid // --------------------------------------------------------------------------- describe("markPaymentPaid", () => { beforeEach(() => { mockFindOneAndUpdate.mockReset(); mockFindOneAndUpdate.mockResolvedValue({ paymentStatus: "paid" }); }); it( "Feature: paypal-payment-integration, Property 5: Capture thành công chuyển sang paid", async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 50 }), fc.string({ minLength: 1, maxLength: 50 }), async (orderId, captureId) => { const { markPaymentPaid } = await import("../summit-requests"); await markPaymentPaid(orderId, captureId); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { paypalOrderId: orderId, paymentStatus: { $ne: "paid" } }, expect.objectContaining({ $set: expect.objectContaining({ paymentStatus: "paid", paypalCaptureId: captureId, }), }), { new: true } ); } ), { numRuns: 50 } ); } ); }); // --------------------------------------------------------------------------- // Property 7: Cancel chuyển trạng thái sang payment_cancelled // --------------------------------------------------------------------------- describe("markPaymentCancelled", () => { beforeEach(() => { mockFindOneAndUpdate.mockReset(); mockFindOneAndUpdate.mockResolvedValue({ paymentStatus: "payment_cancelled" }); }); it( "Feature: paypal-payment-integration, Property 7: Cancel chuyển trạng thái sang payment_cancelled", async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 50 }), async (orderId) => { const { markPaymentCancelled } = await import("../summit-requests"); await markPaymentCancelled(orderId); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { paypalOrderId: orderId }, { $set: { paymentStatus: "payment_cancelled" } }, { new: true } ); } ), { numRuns: 50 } ); } ); }); // --------------------------------------------------------------------------- // Property 14: paymentStatus mặc định là not_required // --------------------------------------------------------------------------- describe("createSummitRequest (default paymentStatus)", () => { beforeEach(() => { mockCreate.mockReset(); mockCreate.mockResolvedValue({}); }); it( "Feature: paypal-payment-integration, Property 14: paymentStatus mặc định là not_required", async () => { // The Mongoose schema sets default: "not_required" — verify the schema field is defined // We test that createSummitRequest does NOT pass paymentStatus (so schema default applies) const { createSummitRequest } = await import("../summit-requests"); await createSummitRequest(mockRow); const callArg = mockCreate.mock.calls[0][0]; // createSummitRequest should not override paymentStatus — schema default handles it expect(callArg).not.toHaveProperty("paymentStatus"); } ); });