From 41e43dda963b1fe7fb15b450eca284748d32325a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 2 Apr 2025 11:59:54 -0400 Subject: [PATCH] feature/IO-2885-IntelliPay-App-Postback-Support - Cleanup --- .../lib/tests/intelliPayGeneralLibs.test.js | 277 +++++++++++++++ server/intellipay/tests/intellipay.test.js | 316 ------------------ 2 files changed, 277 insertions(+), 316 deletions(-) create mode 100644 server/intellipay/lib/tests/intelliPayGeneralLibs.test.js delete mode 100644 server/intellipay/tests/intellipay.test.js diff --git a/server/intellipay/lib/tests/intelliPayGeneralLibs.test.js b/server/intellipay/lib/tests/intelliPayGeneralLibs.test.js new file mode 100644 index 000000000..9c9bec789 --- /dev/null +++ b/server/intellipay/lib/tests/intelliPayGeneralLibs.test.js @@ -0,0 +1,277 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +const getPaymentType = require("../getPaymentType"); +const decodeComment = require("../decodeComment"); +const getCptellerUrl = require("../getCptellerUrl"); +const handlePaymentValidationError = require("../handlePaymentValidationError"); +const getShopCredentials = require("../getShopCredentials"); + +describe("Payment Processing Functions", () => { + // DecodeComment Tests + describe("decodeComment", () => { + it("decodes a valid base64-encoded JSON comment", () => { + const encoded = "eyJ0ZXN0IjoiZGF0YSJ9"; + const expected = { test: "data" }; + expect(decodeComment(encoded)).toEqual(expected); + }); + + it("decodes a complex base64-encoded JSON with payments", () => { + const encoded = "eyJwYXltZW50cyI6W3siam9iaWQiOiIxMjMifV19"; + const expected = { payments: [{ jobid: "123" }] }; + expect(decodeComment(encoded)).toEqual(expected); + }); + + it("returns null when comment is null", () => { + expect(decodeComment(null)).toBeNull(); + }); + + it("returns null when comment is undefined", () => { + expect(decodeComment(undefined)).toBeNull(); + }); + + it("returns null when comment is an empty string", () => { + expect(decodeComment("")).toBeNull(); + }); + + it("returns null when comment is malformed base64", () => { + expect(decodeComment("!@#$%")).toBeNull(); + }); + + it("returns null when comment is valid base64 but not valid JSON", () => { + expect(decodeComment("aW52YWxpZA==")).toBeNull(); + }); + }); + + // GetPaymentType Tests + describe("getPaymentType", () => { + it("returns mapped value when card type exists in mapping", () => { + const ipMapping = { visa: "Visa Card", amex: "American Express" }; + expect(getPaymentType(ipMapping, "visa")).toBe("Visa Card"); + }); + + it("returns original value when card type not in mapping", () => { + const ipMapping = { visa: "Visa Card" }; + expect(getPaymentType(ipMapping, "mastercard")).toBe("mastercard"); + }); + + it("handles lowercase conversion", () => { + const ipMapping = { visa: "Visa Card" }; + expect(getPaymentType(ipMapping, "VISA")).toBe("Visa Card"); + }); + + it("handles null mapping", () => { + expect(getPaymentType(null, "visa")).toBe("visa"); + }); + + it("handles undefined mapping", () => { + expect(getPaymentType(undefined, "visa")).toBe("visa"); + }); + + it("handles empty string card type", () => { + const ipMapping = { visa: "Visa Card" }; + expect(getPaymentType(ipMapping, "")).toBe(""); + }); + + it("handles undefined card type", () => { + const ipMapping = { visa: "Visa Card" }; + expect(getPaymentType(ipMapping, undefined)).toBe(undefined); + }); + }); + + // GetCptellerUrl Tests + describe("getCptellerUrl", () => { + const originalEnv = process.env.NODE_ENV; + + afterEach(() => { + process.env.NODE_ENV = originalEnv; + }); + + it("uses test domain in non-production environment", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ apiType: "webapi" }); + expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); + }); + + it("uses secure domain in production environment", () => { + process.env.NODE_ENV = "production"; + const url = getCptellerUrl({ apiType: "webapi" }); + expect(url).toEqual("https://secure.cpteller.com/api/webapi.cfc"); + }); + + it("adds version number for webapi type", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ apiType: "webapi", version: "26" }); + expect(url).toEqual("https://test.cpteller.com/api/26/webapi.cfc"); + }); + + it("constructs custapi URL without version number", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ apiType: "custapi", version: "26" }); + expect(url).toEqual("https://test.cpteller.com/api/custapi.cfc"); + }); + + it("adds query parameters to the URL", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ + apiType: "webapi", + params: { method: "payment_refund", test: "value" } + }); + expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc?method=payment_refund&test=value"); + }); + + it("handles empty params object", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ apiType: "webapi", params: {} }); + expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); + }); + + it("defaults to webapi when no apiType is provided", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({}); + expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); + }); + + it("combines version and query parameters correctly", () => { + process.env.NODE_ENV = ""; + const url = getCptellerUrl({ + apiType: "webapi", + version: "26", + params: { method: "fee" } + }); + expect(url).toEqual("https://test.cpteller.com/api/26/webapi.cfc?method=fee"); + }); + }); + + // GetShopCredentials Tests + describe("getShopCredentials", () => { + const originalEnv = { ...process.env }; + let mockSend; + + beforeEach(() => { + mockSend = vi.fn(); + vi.mock("@aws-sdk/client-secrets-manager", () => { + return { + SecretsManagerClient: vi.fn(() => ({ + send: mockSend + })), + GetSecretValueCommand: vi.fn((input) => input) + }; + }); + + process.env.INTELLIPAY_MERCHANTKEY = "test-merchant-key"; + process.env.INTELLIPAY_APIKEY = "test-api-key"; + vi.resetModules(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + vi.restoreAllMocks(); + vi.unmock("@aws-sdk/client-secrets-manager"); + }); + + it("returns environment variables in non-production environment", async () => { + process.env.NODE_ENV = "development"; + const result = await getShopCredentials({ imexshopid: "12345" }); + expect(result).toEqual({ + merchantkey: "test-merchant-key", + apikey: "test-api-key" + }); + expect(mockSend).not.toHaveBeenCalled(); + }); + + it("returns undefined when imexshopid is missing in production", async () => { + process.env.NODE_ENV = "production"; + const result = await getShopCredentials({ name: "Test Shop" }); + expect(result).toBeUndefined(); + expect(mockSend).not.toHaveBeenCalled(); + }); + + it("returns undefined for null bodyshop in production", async () => { + process.env.NODE_ENV = "production"; + const result = await getShopCredentials(null); + expect(result).toBeUndefined(); + expect(mockSend).not.toHaveBeenCalled(); + }); + + it("returns undefined for undefined bodyshop in production", async () => { + process.env.NODE_ENV = "production"; + const result = await getShopCredentials(undefined); + expect(result).toBeUndefined(); + expect(mockSend).not.toHaveBeenCalled(); + }); + }); + + // HandlePaymentValidationError Tests + describe("handlePaymentValidationError", () => { + it("logs error and sends 400 response", () => { + const mockLog = vi.fn(); + const mockLogger = { log: mockLog }; + const mockRes = { + status: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis() + }; + + const logCode = "test-validation-error"; + const message = "Invalid data"; + const logMeta = { field: "test", value: 123 }; + + const result = handlePaymentValidationError(mockRes, mockLogger, logCode, message, logMeta); + + expect(mockLog).toHaveBeenCalledWith(logCode, "ERROR", "api", null, { + message, + ...logMeta + }); + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.send).toHaveBeenCalledWith(`Bad Request: ${message}`); + expect(result).toBe(mockRes); + }); + + it("formats different error messages correctly", () => { + const mockLog = vi.fn(); + const mockLogger = { log: mockLog }; + const mockRes = { + status: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis() + }; + + handlePaymentValidationError(mockRes, mockLogger, "error-code", "Custom error"); + expect(mockRes.send).toHaveBeenCalledWith("Bad Request: Custom error"); + }); + + it("passes different logCodes to logger", () => { + const mockLog = vi.fn(); + const mockLogger = { log: mockLog }; + const mockRes = { + status: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis() + }; + + handlePaymentValidationError(mockRes, mockLogger, "custom-log-code", "Error message"); + expect(mockLog).toHaveBeenCalledWith("custom-log-code", "ERROR", "api", null, { message: "Error message" }); + }); + + it("works with minimal logMeta", () => { + const mockLog = vi.fn(); + const mockLogger = { log: mockLog }; + const mockRes = { + status: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis() + }; + + handlePaymentValidationError(mockRes, mockLogger, "error-code", "Error message", {}); + expect(mockLog).toHaveBeenCalledWith("error-code", "ERROR", "api", null, { message: "Error message" }); + }); + + it("works with undefined logMeta", () => { + const mockLog = vi.fn(); + const mockLogger = { log: mockLog }; + const mockRes = { + status: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis() + }; + + handlePaymentValidationError(mockRes, mockLogger, "error-code", "Error message"); + expect(mockLog).toHaveBeenCalledWith("error-code", "ERROR", "api", null, { message: "Error message" }); + }); + }); +}); diff --git a/server/intellipay/tests/intellipay.test.js b/server/intellipay/tests/intellipay.test.js deleted file mode 100644 index 689bfa517..000000000 --- a/server/intellipay/tests/intellipay.test.js +++ /dev/null @@ -1,316 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; - -const getPaymentType = require("../lib/getPaymentType"); -const decodeComment = require("../lib/decodeComment"); -const getCptellerUrl = require("../lib/getCptellerUrl"); -const handlePaymentValidationError = require("../lib/handlePaymentValidationError"); -const getShopCredentials = require("../lib/getShopCredentials"); - -/** - * @description Decode a base64-encoded JSON comment - */ -describe("decodeComment", () => { - it("decodes a valid base64-encoded JSON comment", () => { - // {"test":"data"} encoded in base64 - const encoded = "eyJ0ZXN0IjoiZGF0YSJ9"; - const expected = { test: "data" }; - expect(decodeComment(encoded)).toEqual(expected); - }); - - it("decodes a complex base64-encoded JSON with payments", () => { - // {"payments":[{"jobid":"123"}]} encoded in base64 - const encoded = "eyJwYXltZW50cyI6W3siam9iaWQiOiIxMjMifV19"; - const expected = { payments: [{ jobid: "123" }] }; - expect(decodeComment(encoded)).toEqual(expected); - }); - - it("returns null when comment is null", () => { - expect(decodeComment(null)).toBeNull(); - }); - - it("returns null when comment is undefined", () => { - expect(decodeComment(undefined)).toBeNull(); - }); - - it("returns null when comment is an empty string", () => { - expect(decodeComment("")).toBeNull(); - }); - - it("returns null when comment is malformed base64", () => { - expect(decodeComment("!@#$%")).toBeNull(); - }); - - it("returns null when comment is valid base64 but not valid JSON", () => { - // "invalid" in base64 is "aW52YWxpZA==" - expect(decodeComment("aW52YWxpZA==")).toBeNull(); - }); -}); - -/** - * @description Get the payment type based on the card type - */ -describe("getPaymentType", () => { - it("returns mapped value when card type exists in mapping", () => { - const ipMapping = { visa: "Visa Card", amex: "American Express" }; - expect(getPaymentType(ipMapping, "visa")).toBe("Visa Card"); - }); - - it("returns original value when card type not in mapping", () => { - const ipMapping = { visa: "Visa Card" }; - expect(getPaymentType(ipMapping, "mastercard")).toBe("mastercard"); - }); - - it("handles lowercase conversion", () => { - const ipMapping = { visa: "Visa Card" }; - expect(getPaymentType(ipMapping, "VISA")).toBe("Visa Card"); - }); - - it("handles null mapping", () => { - expect(getPaymentType(null, "visa")).toBe("visa"); - }); - - it("handles undefined mapping", () => { - expect(getPaymentType(undefined, "visa")).toBe("visa"); - }); - - it("handles empty string card type", () => { - const ipMapping = { visa: "Visa Card" }; - expect(getPaymentType(ipMapping, "")).toBe(""); - }); - - it("handles undefined card type", () => { - const ipMapping = { visa: "Visa Card" }; - expect(getPaymentType(ipMapping, undefined)).toBe(undefined); - }); -}); - -/** - * @description Get the CPTeller URL based on environment and parameters - */ -describe("getCptellerUrl", () => { - const originalEnv = process.env.NODE_ENV; - - afterEach(() => { - // Restore the original NODE_ENV after each test - process.env.NODE_ENV = originalEnv; - }); - - it("uses test domain in non-production environment", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ apiType: "webapi" }); - expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); - }); - - it("uses secure domain in production environment", () => { - process.env.NODE_ENV = "production"; - const url = getCptellerUrl({ apiType: "webapi" }); - expect(url).toEqual("https://secure.cpteller.com/api/webapi.cfc"); - }); - - it("adds version number for webapi type", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ apiType: "webapi", version: "26" }); - expect(url).toEqual("https://test.cpteller.com/api/26/webapi.cfc"); - }); - - it("constructs custapi URL without version number", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ apiType: "custapi", version: "26" }); - expect(url).toEqual("https://test.cpteller.com/api/custapi.cfc"); - }); - - it("adds query parameters to the URL", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ - apiType: "webapi", - params: { method: "payment_refund", test: "value" } - }); - expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc?method=payment_refund&test=value"); - }); - - it("handles empty params object", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ apiType: "webapi", params: {} }); - expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); - }); - - it("defaults to webapi when no apiType is provided", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({}); - expect(url).toEqual("https://test.cpteller.com/api/webapi.cfc"); - }); - - it("combines version and query parameters correctly", () => { - process.env.NODE_ENV = ""; - const url = getCptellerUrl({ - apiType: "webapi", - version: "26", - params: { method: "fee" } - }); - expect(url).toEqual("https://test.cpteller.com/api/26/webapi.cfc?method=fee"); - }); -}); - -/** - * @description Get shop credentials from AWS Secrets Manager or environment variables - */ - -describe("getShopCredentials", () => { - const originalEnv = { ...process.env }; - let mockSend; - - beforeEach(() => { - // Create a mock function for send - mockSend = vi.fn(); - - // Mock the entire AWS SDK module - vi.mock("@aws-sdk/client-secrets-manager", () => { - return { - SecretsManagerClient: vi.fn(() => ({ - send: mockSend - })), - GetSecretValueCommand: vi.fn((input) => input) - }; - }); - - // Setup test environment variables - process.env.INTELLIPAY_MERCHANTKEY = "test-merchant-key"; - process.env.INTELLIPAY_APIKEY = "test-api-key"; - - // Clear module cache to ensure fresh mock is used - vi.resetModules(); - }); - - afterEach(() => { - // Restore environment and clear mocks - process.env = { ...originalEnv }; - vi.restoreAllMocks(); - vi.unmock("@aws-sdk/client-secrets-manager"); - }); - - it("returns environment variables in non-production environment", async () => { - process.env.NODE_ENV = "development"; - - const result = await getShopCredentials({ imexshopid: "12345" }); - - expect(result).toEqual({ - merchantkey: "test-merchant-key", - apikey: "test-api-key" - }); - expect(mockSend).not.toHaveBeenCalled(); - }); - - it("returns undefined when imexshopid is missing in production", async () => { - process.env.NODE_ENV = "production"; - - const result = await getShopCredentials({ name: "Test Shop" }); - - expect(result).toBeUndefined(); - expect(mockSend).not.toHaveBeenCalled(); - }); - - it("returns undefined for null bodyshop in production", async () => { - process.env.NODE_ENV = "production"; - - const result = await getShopCredentials(null); - - expect(result).toBeUndefined(); - expect(mockSend).not.toHaveBeenCalled(); - }); - - it("returns undefined for undefined bodyshop in production", async () => { - process.env.NODE_ENV = "production"; - - const result = await getShopCredentials(undefined); - - expect(result).toBeUndefined(); - expect(mockSend).not.toHaveBeenCalled(); - }); -}); - -/** - * @description Handle payment validation errors - */ -describe("handlePaymentValidationError", () => { - it("logs error and sends 400 response", () => { - // Create mock objects - const mockLog = vi.fn(); - const mockLogger = { log: mockLog }; - const mockRes = { - status: vi.fn().mockReturnThis(), - send: vi.fn().mockReturnThis() - }; - - // Test data - const logCode = "test-validation-error"; - const message = "Invalid data"; - const logMeta = { field: "test", value: 123 }; - - // Call the function - const result = handlePaymentValidationError(mockRes, mockLogger, logCode, message, logMeta); - - // Verify logger.log was called correctly - expect(mockLog).toHaveBeenCalledWith(logCode, "ERROR", "api", null, { message, ...logMeta }); - - // Verify res.status was called with 400 - expect(mockRes.status).toHaveBeenCalledWith(400); - - // Verify res.send was called with correct message - expect(mockRes.send).toHaveBeenCalledWith(`Bad Request: ${message}`); - - // Verify the function returns the response - expect(result).toBe(mockRes); - }); - - it("formats different error messages correctly", () => { - const mockLog = vi.fn(); - const mockLogger = { log: mockLog }; - const mockRes = { - status: vi.fn().mockReturnThis(), - send: vi.fn().mockReturnThis() - }; - - handlePaymentValidationError(mockRes, mockLogger, "error-code", "Custom error"); - - expect(mockRes.send).toHaveBeenCalledWith("Bad Request: Custom error"); - }); - - it("passes different logCodes to logger", () => { - const mockLog = vi.fn(); - const mockLogger = { log: mockLog }; - const mockRes = { - status: vi.fn().mockReturnThis(), - send: vi.fn().mockReturnThis() - }; - - handlePaymentValidationError(mockRes, mockLogger, "custom-log-code", "Error message"); - - expect(mockLog).toHaveBeenCalledWith("custom-log-code", "ERROR", "api", null, { message: "Error message" }); - }); - - it("works with minimal logMeta", () => { - const mockLog = vi.fn(); - const mockLogger = { log: mockLog }; - const mockRes = { - status: vi.fn().mockReturnThis(), - send: vi.fn().mockReturnThis() - }; - - handlePaymentValidationError(mockRes, mockLogger, "error-code", "Error message", {}); - - expect(mockLog).toHaveBeenCalledWith("error-code", "ERROR", "api", null, { message: "Error message" }); - }); - - it("works with undefined logMeta", () => { - const mockLog = vi.fn(); - const mockLogger = { log: mockLog }; - const mockRes = { - status: vi.fn().mockReturnThis(), - send: vi.fn().mockReturnThis() - }; - - handlePaymentValidationError(mockRes, mockLogger, "error-code", "Error message"); - - expect(mockLog).toHaveBeenCalledWith("error-code", "ERROR", "api", null, { message: "Error message" }); - }); -});