Skip to main content
Glama

yeepay-mcp

by yop-platform
integration.test.ts17.1 kB
// tests/integration.test.ts import { jest, describe, it, expect, beforeEach, beforeAll, } from "@jest/globals"; import type { CreatePaymentSuccessResponse, PaymentRequest as CreatePaymentInput, } from "../src/tools/createPayment.js"; // Import PaymentRequest as CreatePaymentInput import type { QueryRequest as QueryPaymentInput, YeepayQueryResult as QueryPaymentResult, } from "../src/tools/queryPayment.js"; // Import QueryRequest as QueryPaymentInput and YeepayQueryResult as QueryPaymentResult // --- Configuration for Mock/Real API --- const useRealAPI = process.env.USE_REAL_API === "true"; console.log( `Running integration tests with ${useRealAPI ? "REAL API" : "MOCKED API"}`, ); // --- Conditional Mocking --- if (!useRealAPI) { // Mock the implementation files only if not using the real API jest.mock("../src/tools/createPayment.js"); jest.mock("../src/tools/queryPayment.js"); // Mock the config module only if not using the real API jest.mock("../src/config.js", () => ({ config: { parentMerchantNo: "mockParentMerchantNo123", merchantNo: "mockMerchantNo456", appPrivateKey: "mockSecretKey789", appKey: "mockAppKey101", notifyUrl: "http://mock.test/notify", yopPublicKey: "-----BEGIN PUBLIC KEY-----\nMOCK_YOP_PUBLIC_KEY_CONTENT\n-----END PUBLIC KEY-----", }, })); } else { // Ensure dotenv is loaded if running real API tests directly (e.g., via IDE) // In CI/CD, env vars should be set directly. try { // eslint-disable-next-line @typescript-eslint/no-require-imports require("dotenv").config({ path: ".env" }); // Adjust path if needed } catch { console.warn( "dotenv not found or failed to load. Ensure environment variables are set for real API tests.", ); } // Validate required env vars for real API calls const requiredEnvVars = [ "YOP_PARENT_MERCHANT_NO", "YOP_MERCHANT_NO", "YOP_APP_KEY", "YOP_APP_PRIVATE_KEY", ]; // Corrected prefix const missingVars = requiredEnvVars.filter((v) => !process.env[v]); if (missingVars.length > 0) { throw new Error( `Missing required environment variables for real API tests: ${missingVars.join(", ")}`, ); } } // --- Type Definitions --- // Use the actual function types type CreateWebpageYeepayPaymentType = typeof import("../src/tools/createPayment.js").createWebpageYeepayPayment; type QueryYeepayPaymentStatusType = typeof import("../src/tools/queryPayment.js").queryYeepayPaymentStatus; // Define a union type for the functions, which can be either the real function or the mock // eslint-disable-next-line @typescript-eslint/no-explicit-any type PaymentFunction<T extends (...args: any) => any> = | T | jest.MockedFunction<T>; // Define a minimal structure for the mock based on its usage in queryPayment.ts // Keep this for mock response structure definition interface MockYeepayQueryResult { code: string; message: string; orderId: string; uniqueOrderNo: string; status: string; } // --- Test Suite --- // Use describe.skip if running real API tests to avoid accidental runs/costs, // unless explicitly intended. // Use describe directly, conditional logic moved inside // const describeMocked = describe; // Or keep if preferred describe("Yeepay Payment Integration Flow (Mocked)", () => { // Skip this entire block if running real API tests if (useRealAPI) { // Optional: Add a console log to indicate skipping // Removed commented debug log return; } // Variables to hold either the real functions or the mocks let createPayment: PaymentFunction<CreateWebpageYeepayPaymentType>; let queryPayment: PaymentFunction<QueryYeepayPaymentStatusType>; beforeEach(async () => { if (!useRealAPI) { // Dynamically import the MOCKED modules const createPaymentMockModule = await import( "../src/tools/createPayment.js" ); const queryPaymentMockModule = await import( "../src/tools/queryPayment.js" ); // Assign the imported mock functions createPayment = createPaymentMockModule.createWebpageYeepayPayment as jest.MockedFunction<CreateWebpageYeepayPaymentType>; queryPayment = queryPaymentMockModule.queryYeepayPaymentStatus as jest.MockedFunction<QueryYeepayPaymentStatusType>; // Reset mocks before each test // eslint-disable-next-line @typescript-eslint/no-explicit-any (createPayment as jest.MockedFunction<any>).mockClear(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (queryPayment as jest.MockedFunction<any>).mockClear(); } else { // Dynamically import the REAL modules // We need to bypass the cache potentially set by jest.mock if it ran in a previous non-skipped describe block jest.unmock("../src/tools/createPayment.js"); jest.unmock("../src/tools/queryPayment.js"); jest.unmock("../src/config.js"); // Ensure config is unmocked too const createPaymentRealModule = await import( "../src/tools/createPayment.js" ); const queryPaymentRealModule = await import( "../src/tools/queryPayment.js" ); await import("../src/config.js"); // Import config to ensure it's loaded createPayment = createPaymentRealModule.createWebpageYeepayPayment; queryPayment = queryPaymentRealModule.queryYeepayPaymentStatus; } }); it("should create a payment and then query its status successfully", async () => { // --- Arrange --- const testOrderId = `MOCK_TEST_${Date.now()}`; const testAmount = 0.01; const testGoodsName = "Mock Test Product"; const createInput: CreatePaymentInput = { orderId: testOrderId, amount: testAmount, goodsName: testGoodsName, }; let expectedUniqueOrderNo: string | undefined; let expectedPrePayTn: string | undefined; if (!useRealAPI) { const mockUniqueOrderNo = `MOCK_YOP_${testOrderId}`; const mockPrePayTn = `MOCK_TN_${testOrderId}`; expectedUniqueOrderNo = mockUniqueOrderNo; expectedPrePayTn = mockPrePayTn; const mockCreateResponse: CreatePaymentSuccessResponse = { prePayTn: mockPrePayTn, orderId: testOrderId, uniqueOrderNo: mockUniqueOrderNo, }; // eslint-disable-next-line @typescript-eslint/no-explicit-any (createPayment as jest.MockedFunction<any>).mockResolvedValue( mockCreateResponse, ); const mockQueryResponse: MockYeepayQueryResult = { code: "OPR00000", message: "查询成功", orderId: testOrderId, uniqueOrderNo: mockUniqueOrderNo, status: "SUCCESS", }; // eslint-disable-next-line @typescript-eslint/no-explicit-any (queryPayment as jest.MockedFunction<any>).mockResolvedValue( mockQueryResponse as any, ); } // --- Act --- // 1. Call create payment const createResponse = await createPayment(createInput); // 2. Extract uniqueOrderNo const uniqueOrderNoFromCreate = createResponse.uniqueOrderNo; const orderIdFromCreate = createResponse.orderId; // 3. Call query payment status const queryInput: QueryPaymentInput = { orderId: orderIdFromCreate, }; // Add a delay for real API to allow processing if (useRealAPI) await new Promise((resolve) => setTimeout(resolve, 3000)); // 3-second delay const queryResponse = await queryPayment(queryInput); // --- Assert --- // Common assertions for both mock and real expect(createResponse).toBeDefined(); expect(createResponse.orderId).toBe(testOrderId); expect(createResponse.uniqueOrderNo).toBeDefined(); expect(createResponse.uniqueOrderNo).not.toBe(""); expect(uniqueOrderNoFromCreate).toBe(createResponse.uniqueOrderNo); // Check consistency expect(queryResponse).toBeDefined(); expect(queryResponse.orderId).toBe(testOrderId); expect(queryResponse.uniqueOrderNo).toBe(createResponse.uniqueOrderNo); // Should match the one from creation if (!useRealAPI) { // Mock-specific assertions expect(createPayment).toHaveBeenCalledTimes(1); expect(createPayment).toHaveBeenCalledWith(createInput); expect(createResponse.prePayTn).toBe(expectedPrePayTn); expect(createResponse.uniqueOrderNo).toBe(expectedUniqueOrderNo); expect(queryPayment).toHaveBeenCalledTimes(1); expect(queryPayment).toHaveBeenCalledWith(queryInput); expect(queryResponse.status).toBe("SUCCESS"); // Mock returns SUCCESS expect(queryResponse.code).toBe("OPR00000"); } else { // Real API assertions (more flexible) expect(createResponse.prePayTn).toBeDefined(); // Real API should return this expect(createResponse.prePayTn).not.toBe(""); // Real API status might be PROCESSING initially, or SUCCESS if checked later expect(["PROCESSING", "SUCCESS", "PAY_SUCCESS"]).toContain( queryResponse.status, ); expect(queryResponse.code).toBe("OPR00000"); // Success code for query itself // Add more real API specific checks if needed, e.g., amount // expect(queryResponse.orderAmount).toBe(testAmount.toString()); // API might return string amount } }); // Test case for creation failure (Mock only, real API failure is harder to reliably trigger) it("should handle error during payment creation (Mocked)", async () => { if (useRealAPI) { console.warn( "Skipping mock-specific creation failure test in REAL API mode.", ); return; // Skip this test if using real API } // --- Arrange --- const testOrderId = `MOCK_FAIL_CREATE_${Date.now()}`; const creationError = new Error( "Yeepay API Failure: AUTH_ERROR - Invalid credentials", ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (createPayment as jest.MockedFunction<any>).mockRejectedValue( creationError, ); // --- Act & Assert --- await expect( createPayment({ orderId: testOrderId, amount: 0.02, goodsName: "Fail Product", }), ).rejects.toThrow(creationError); expect(queryPayment).not.toHaveBeenCalled(); }); // Test case for query failure (Mock only) it("should handle error during payment status query (Mocked)", async () => { if (useRealAPI) { console.warn( "Skipping mock-specific query failure test in REAL API mode.", ); return; // Skip this test if using real API } // --- Arrange --- const testOrderId = `MOCK_FAIL_QUERY_${Date.now()}`; const testUniqueOrderNo = `MOCK_YOP_${testOrderId}`; const testPrePayTn = `MOCK_TN_${testOrderId}`; const mockCreateResponse: CreatePaymentSuccessResponse = { prePayTn: testPrePayTn, orderId: testOrderId, uniqueOrderNo: testUniqueOrderNo, }; // eslint-disable-next-line @typescript-eslint/no-explicit-any (createPayment as jest.MockedFunction<any>).mockResolvedValue( mockCreateResponse, ); const queryError = new Error( "Yeepay Business Error: BIZ_ORDER_NOT_EXIST - Order not found", ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (queryPayment as jest.MockedFunction<any>).mockRejectedValue(queryError); // --- Act --- // 1. Call create payment (expected to succeed) const createResponse = await createPayment({ orderId: testOrderId, amount: 0.03, goodsName: "Query Fail Product", }); // 2. Call query payment status (expected to fail) // --- Assert --- await expect( queryPayment({ orderId: createResponse.orderId, }), ).rejects.toThrow(queryError); expect(createPayment).toHaveBeenCalledTimes(1); expect(queryPayment).toHaveBeenCalledTimes(1); expect(queryPayment).toHaveBeenCalledWith({ orderId: testOrderId }); }); // --- Add a separate describe block for REAL API tests --- // This allows running them explicitly if needed, e.g., `jest -t "Real API"` describe("Yeepay Payment Integration Flow (Real API)", () => { // Skip this entire block if not using real API beforeAll(() => { // Skip logic removed from beforeAll. Rely on checks within 'it' blocks. // Keep environment variable validation for real API runs. if (useRealAPI) { // Validate required env vars again just in case const requiredEnvVars = [ "YOP_PARENT_MERCHANT_NO", "YOP_MERCHANT_NO", "YOP_APP_KEY", "YOP_APP_PRIVATE_KEY", ]; // Corrected prefix const missingVars = requiredEnvVars.filter((v) => !process.env[v]); if (missingVars.length > 0) { throw new Error( `Missing required environment variables for real API tests: ${missingVars.join(", ")}`, ); } } }); // Variables to hold the real functions let createPaymentReal: CreateWebpageYeepayPaymentType; let queryPaymentReal: QueryYeepayPaymentStatusType; beforeEach(async () => { if (!useRealAPI) return; // Don't setup if not running real tests // Dynamically import the REAL modules, ensuring they are not mocked jest.unmock("../src/tools/createPayment.js"); jest.unmock("../src/tools/queryPayment.js"); jest.unmock("../src/config.js"); const createPaymentRealModule = await import( "../src/tools/createPayment.js" ); const queryPaymentRealModule = await import( "../src/tools/queryPayment.js" ); await import("../src/config.js"); // Ensure real config is loaded createPaymentReal = createPaymentRealModule.createWebpageYeepayPayment; queryPaymentReal = queryPaymentRealModule.queryYeepayPaymentStatus; }); // Define conditional 'it' based on the environment variable const itReal = useRealAPI ? it : it.skip; // Restore conditional execution // Use the conditional 'itReal' itReal( "should create a real payment and query its status", async () => { // The internal if block is no longer needed as itReal handles skipping // --- Arrange --- const testOrderId = `REAL_${Date.now()}`; // Use a distinct prefix const testAmount = 0.01; // Use the minimum allowed amount const testGoodsName = "Real API Test Product"; const createInput: CreatePaymentInput = { orderId: testOrderId, amount: testAmount, goodsName: testGoodsName, // Add userIp if required by your real implementation/config // userIp: '127.0.0.1' }; console.log( `Attempting to create real payment with orderId: ${testOrderId}`, ); // --- Act --- let createResponse: CreatePaymentSuccessResponse; try { createResponse = await createPaymentReal(createInput); console.log(`Real payment creation successful:`, createResponse); } catch (error) { console.error("Real payment creation failed:", error); throw error; // Fail the test if creation fails } // Add a significant delay for the real API to process the order console.log("Waiting 5 seconds before querying status..."); await new Promise((resolve) => setTimeout(resolve, 5000)); // 5-second delay const queryInput: QueryPaymentInput = { orderId: createResponse.orderId, }; let queryResponse: QueryPaymentResult; try { queryResponse = await queryPaymentReal(queryInput); console.log(`Real payment query successful:`, queryResponse); } catch (error) { console.error("Real payment query failed:", error); throw error; // Fail the test if query fails } // --- Assert --- expect(createResponse).toBeDefined(); expect(createResponse.orderId).toBe(testOrderId); expect(createResponse.uniqueOrderNo).toBeDefined(); expect(createResponse.uniqueOrderNo).not.toBe(""); expect(createResponse.prePayTn).toBeDefined(); // Real API should return this expect(createResponse.prePayTn).not.toBe(""); expect(queryResponse).toBeDefined(); expect(queryResponse.orderId).toBe(testOrderId); expect(queryResponse.uniqueOrderNo).toBe(createResponse.uniqueOrderNo); expect(queryResponse.code).toBe("OPR00000"); // Query itself succeeded // Status might be PROCESSING or PAY_SUCCESS/SUCCESS depending on timing and if payment was completed externally // It's unlikely to be SUCCESS immediately unless the test environment auto-pays. // Check for non-failure statuses. expect(["PROCESSING", "PAY_SUCCESS", "SUCCESS"]).toContain( queryResponse.status, ); // Optional: Check amount if available and consistent in the query response // expect(queryResponse.payAmount).toBe(testAmount.toString()); // Adjust field name and type as needed }, 15000, ); // Increase timeout for real API calls }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yop-platform/yeepay-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server