feature/IO-2885-IntelliPay-App-Postback

- Add Tests
This commit is contained in:
Dave Richer
2025-04-02 11:34:48 -04:00
parent 9bf6ba9cf0
commit 09c4662436
5 changed files with 332 additions and 8 deletions

48
package-lock.json generated
View File

@@ -75,6 +75,7 @@
"eslint": "^9.23.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.15.0",
"mock-require": "^3.0.3",
"p-limit": "^3.1.0",
"prettier": "^3.5.3",
"source-map-explorer": "^2.5.2",
@@ -8402,9 +8403,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9535,6 +9536,40 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mock-require": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
"integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-caller-file": "^1.0.2",
"normalize-path": "^2.1.1"
},
"engines": {
"node": ">=4.3.0"
}
},
"node_modules/mock-require/node_modules/get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true,
"license": "ISC"
},
"node_modules/mock-require/node_modules/normalize-path": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
"integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"remove-trailing-separator": "^1.0.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/module-details-from-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
@@ -10709,6 +10744,13 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
"integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
"dev": true,
"license": "ISC"
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",

View File

@@ -81,6 +81,7 @@
"eslint": "^9.23.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.15.0",
"mock-require": "^3.0.3",
"p-limit": "^3.1.0",
"prettier": "^3.5.3",
"source-map-explorer": "^2.5.2",

View File

@@ -0,0 +1,152 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import mockRequire from "mock-require";
const gqlRequestMock = { request: vi.fn() };
const getPaymentTypeMock = vi.fn(() => "American Express");
const sendPaymentNotificationEmailMock = vi.fn();
let handleCommentBasedPayment;
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
// Mock dependencies using mock-require BEFORE requiring the target module
mockRequire("../../../graphql-client/graphql-client", {
client: gqlRequestMock
});
mockRequire("../getPaymentType", getPaymentTypeMock);
mockRequire("../sendPaymentNotificationEmail", sendPaymentNotificationEmailMock);
// Now require the module under test
handleCommentBasedPayment = require("../handleCommentBasedPayment");
// Chain your GraphQL mocks
gqlRequestMock.request
.mockResolvedValueOnce({
jobs: [
{
id: "c1ffe09c-e7d4-46b3-aac5-f23e39563181",
shopid: "bfec8c8c-b7f1-49e0-be4c-524455f4e582"
}
]
})
.mockResolvedValueOnce({
bodyshops_by_pk: {
id: "bfec8c8c-b7f1-49e0-be4c-524455f4e582",
intellipay_config: {
payment_map: {
amex: "American Express"
}
}
}
})
.mockResolvedValueOnce({
insert_payments: {
returning: [{ id: "5dfda3c4-c0a6-4b09-a73d-176ed0ac6499" }]
}
});
});
describe("handleCommentBasedPayment", () => {
const mockLogger = { log: vi.fn() };
const mockRes = { sendStatus: vi.fn() };
const values = {
authcode: "5557301",
total: "0.01",
origin: "Dejavoo",
paymentid: "24294378",
cardtype: "Amex"
};
const decodedComment = {
payments: [{ jobid: "c1ffe09c-e7d4-46b3-aac5-f23e39563181", amount: 0.01 }],
userEmail: "test@example.com"
};
const logMeta = { op: "xyz123" };
it("processes comment-based payment and returns 200", async () => {
await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);
expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
expect(getPaymentTypeMock).toHaveBeenCalledWith({ amex: "American Express" }, "Amex");
expect(sendPaymentNotificationEmailMock).not.toHaveBeenCalled();
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
});
it("sends notification if origin is OneLink and userEmail exists", async () => {
const oneLinkValues = { ...values, origin: "OneLink" };
await handleCommentBasedPayment(oneLinkValues, decodedComment, mockLogger, logMeta, mockRes);
expect(sendPaymentNotificationEmailMock).toHaveBeenCalledWith(
"test@example.com",
expect.anything(),
expect.anything(),
mockLogger,
logMeta
);
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
});
it("handles decodedComment as a direct array", async () => {
const arrayComment = [{ jobid: "c1ffe09c-e7d4-46b3-aac5-f23e39563181", amount: 0.01 }];
await handleCommentBasedPayment(values, arrayComment, mockLogger, logMeta, mockRes);
expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
});
it("does not send email if origin is OneLink but userEmail is missing", async () => {
const commentWithoutEmail = {
payments: decodedComment.payments
// no userEmail
};
const oneLinkValues = { ...values, origin: "OneLink" };
await handleCommentBasedPayment(oneLinkValues, commentWithoutEmail, mockLogger, logMeta, mockRes);
expect(sendPaymentNotificationEmailMock).not.toHaveBeenCalled();
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
});
it("logs important stages of the process", async () => {
await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);
const logCalls = mockLogger.log.mock.calls.map(([tag]) => tag);
expect(logCalls).toContain("intellipay-postback-parsed-comment");
expect(logCalls).toContain("intellipay-postback-payment-success");
});
it("handles missing payment_map safely", async () => {
gqlRequestMock.request.mockReset(); // 🧹 Clear previous .mockResolvedValueOnce calls
gqlRequestMock.request
.mockResolvedValueOnce({
jobs: [{ id: "job1", shopid: "shop1" }]
})
.mockResolvedValueOnce({
bodyshops_by_pk: {
id: "shop1",
intellipay_config: null
}
})
.mockResolvedValueOnce({
insert_payments: {
returning: [{ id: "payment1" }]
}
});
await handleCommentBasedPayment(values, decodedComment, mockLogger, logMeta, mockRes);
expect(getPaymentTypeMock).toHaveBeenCalledWith(undefined, "Amex");
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
});
});

View File

@@ -0,0 +1,129 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import mockRequire from "mock-require";
const gqlRequestMock = { request: vi.fn() };
const getPaymentTypeMock = vi.fn(() => "Visa");
const handlePaymentValidationErrorMock = vi.fn();
let handleInvoiceBasedPayment;
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
mockRequire("../../../graphql-client/graphql-client", {
client: gqlRequestMock
});
mockRequire("../getPaymentType", getPaymentTypeMock);
mockRequire("../handlePaymentValidationError", handlePaymentValidationErrorMock);
handleInvoiceBasedPayment = require("../handleInvoiceBasedPayment");
gqlRequestMock.request
.mockResolvedValueOnce({
jobs: [
{
id: "job123",
bodyshop: {
id: "shop123",
intellipay_config: {
payment_map: {
visa: "Visa"
}
}
}
}
]
})
.mockResolvedValueOnce({
id: "payment123"
})
.mockResolvedValueOnce({
insert_payment_response: {
returning: [{ id: "response123" }]
}
});
});
describe("handleInvoiceBasedPayment", () => {
const mockLogger = { log: vi.fn() };
const mockRes = { sendStatus: vi.fn() };
const values = {
merchantid: "m123",
invoice: "INV-001",
total: 100.0,
authcode: "AUTH123",
cardtype: "visa",
paymentid: "P789"
};
const logMeta = { op: "abc123" };
it("processes a valid invoice-based payment", async () => {
await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);
expect(gqlRequestMock.request).toHaveBeenCalledTimes(3);
expect(getPaymentTypeMock).toHaveBeenCalledWith({ visa: "Visa" }, "visa");
expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
expect(handlePaymentValidationErrorMock).not.toHaveBeenCalled();
});
it("handles missing merchantid with validation error", async () => {
const invalidValues = { ...values, merchantid: undefined };
await handleInvoiceBasedPayment(invalidValues, mockLogger, logMeta, mockRes);
expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
mockRes,
mockLogger,
"intellipay-postback-no-merchantid",
"Merchant ID is missing",
logMeta
);
expect(gqlRequestMock.request).not.toHaveBeenCalled();
});
it("handles job not found with validation error", async () => {
gqlRequestMock.request.mockReset();
gqlRequestMock.request.mockResolvedValueOnce({ jobs: [] });
await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);
expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
mockRes,
mockLogger,
"intellipay-postback-job-not-found",
"Job not found",
logMeta
);
});
it("handles missing bodyshop with validation error", async () => {
gqlRequestMock.request.mockReset();
gqlRequestMock.request.mockResolvedValueOnce({
jobs: [{ id: "job123", bodyshop: null }]
});
await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);
expect(handlePaymentValidationErrorMock).toHaveBeenCalledWith(
mockRes,
mockLogger,
"intellipay-postback-bodyshop-not-found",
"Bodyshop not found",
logMeta
);
});
it("logs all expected stages of the process", async () => {
await handleInvoiceBasedPayment(values, mockLogger, logMeta, mockRes);
const logTags = mockLogger.log.mock.calls.map(([tag]) => tag);
expect(logTags).toContain("intellipay-postback-invoice-job-fetched");
expect(logTags).toContain("intellipay-postback-invoice-payment-success");
expect(logTags).toContain("intellipay-postback-invoice-response-success");
});
});

View File

@@ -1,10 +1,10 @@
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");
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