diff --git a/.gitignore b/.gitignore index bf7f3dac7..9b2ed79f9 100644 --- a/.gitignore +++ b/.gitignore @@ -127,4 +127,4 @@ vitest-report*/ vitest-coverage/ *.vitest.log test-output.txt - +server/job/test/fixtures diff --git a/Dockerfile b/Dockerfile index 8afe15df4..bab946292 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,4 +56,5 @@ COPY . . EXPOSE 4000 9229 # Start the application -CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"] +RUN echo "Starting the application..." +CMD ["nodemon", "--ignore", "./server/job/test/fixtures", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"] diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index dd850dc9b..8cde6ebb3 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -1,8 +1,7 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -// const adminClient = require("../graphql-client/graphql-client").client; -// const _ = require("lodash"); const logger = require("../utils/logger"); +const { captureFixture } = require("./utils/seralizeHelper"); const InstanceMgr = require("../utils/instanceMgr").default; //****************************************************** */ @@ -33,7 +32,15 @@ exports.totalsSsu = async function (req, res) { id: id }); - const newTotals = await TotalsServerSide({ body: { job: job.jobs_by_pk, client: client } }, res, true); + // Extract the job data (the input for TotalsServerSide) + const inputForTotals = job.jobs_by_pk; + + const newTotals = await TotalsServerSide({ body: { job: inputForTotals, client: client } }, res, true); + + // Capture fixture data (input and output), using job.id for the filename. + if (process.env?.SAVE_TOTALS_DATA === "true") { + captureFixture(inputForTotals, newTotals); + } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { jobId: id, @@ -45,9 +52,11 @@ exports.totalsSsu = async function (req, res) { } }); - if (result) { - res.status(200).send(); + if (!result) { + throw new Error("Failed to update job totals"); } + + res.status(200).send(); } catch (error) { logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, { jobid: id, @@ -58,7 +67,7 @@ exports.totalsSsu = async function (req, res) { } }; -//IMPORTANT*** These two functions MUST be mirrrored. +//IMPORTANT*** These two functions MUST be mirrored. async function TotalsServerSide(req, res) { const { job, client } = req.body; await AtsAdjustmentsIfRequired({ job: job, client: client, user: req?.user }); @@ -133,6 +142,9 @@ async function TotalsServerSide(req, res) { } } +// Exported for testing purposes. +exports.TotalsServerSide = TotalsServerSide; + async function Totals(req, res) { const { job, id } = req.body; diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 6182f9ced..3aba47d1f 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -1,6 +1,7 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); +const { captureFixture } = require("./utils/seralizeHelper"); //****************************************************** */ //****************************************************** */ @@ -30,7 +31,16 @@ exports.totalsSsu = async function (req, res) { id: id }); - const newTotals = await TotalsServerSide({ body: { job: job.jobs_by_pk, client: client } }, res, true); + // Extract the job data (the input for TotalsServerSide) + const inputForTotals = job.jobs_by_pk; + + // Capture the output of TotalsServerSide + const newTotals = await TotalsServerSide({ body: { job: inputForTotals, client: client } }, res, true); + + // Capture fixture data (input and output), using job.id for the filename. + if (process.env?.SAVE_TOTALS_DATA === "true") { + captureFixture(inputForTotals, newTotals); + } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { jobId: id, @@ -57,7 +67,7 @@ exports.totalsSsu = async function (req, res) { } }; -//IMPORTANT*** These two functions MUST be mirrrored. +//IMPORTANT*** These two functions MUST be mirrored. async function TotalsServerSide(req, res) { const { job, client } = req.body; await AtsAdjustmentsIfRequired({ job: job, client: client, user: req?.user }); @@ -81,6 +91,9 @@ async function TotalsServerSide(req, res) { } } +// Exported for testing purposes +exports.TotalsServerSide = TotalsServerSide; + async function Totals(req, res) { const { job, id } = req.body; diff --git a/server/job/test/job-totals.test.js b/server/job/test/job-totals.test.js new file mode 100644 index 000000000..059b0bb1e --- /dev/null +++ b/server/job/test/job-totals.test.js @@ -0,0 +1,71 @@ +import fs from "fs"; +import path from "path"; +import { describe, it, expect } from "vitest"; +import { TotalsServerSide as TotalsServerSideCA } from "../job-totals"; // Canadian version (imex) +import { TotalsServerSide as TotalsServerSideUS } from "../job-totals-USA"; // US version (rome) + +/** + * This function is used to replace the values in the object with their toObject() representation. + * @param key + * @param value + * @returns {*} + */ +const dineroReplacer = (key, value) => { + if (value && typeof value === "object" && typeof value.toObject === "function") { + return value.toObject(); + } + return value; +}; + +/** + * Normalizes the output of the TotalsServerSide function by converting + * @param obj + * @returns {any} + */ +const normalizeOutput = (obj) => { + return JSON.parse(JSON.stringify(obj, dineroReplacer)); +}; + +/** + * This test suite is designed to validate the functionality of the TotalsServerSide function + */ +describe("TotalsServerSide fixture tests", () => { + const fixturesDir = path.join(__dirname, "fixtures", "job-totals"); + + const fixtureFiles = fs.readdirSync(fixturesDir).filter((f) => f.endsWith(".json")); + + const dummyClient = { + request: async () => { + return {}; + } + }; + + const dummyRes = { + status: () => ({ send: () => {} }) + }; + + fixtureFiles.forEach((file) => { + it(`should produce matching output for fixture file ${file}`, async () => { + const fixturePath = path.join(fixturesDir, file); + const fixtureData = JSON.parse(fs.readFileSync(fixturePath, "utf8")); + + const { environment, input, output: expectedOutput } = fixtureData; + + const req = { + body: { + job: input, + client: dummyClient + }, + user: {} + }; + + const computedOutput = + environment === "us" ? await TotalsServerSideUS(req, dummyRes) : await TotalsServerSideCA(req, dummyRes); + + const normalizedComputed = normalizeOutput(computedOutput); + const normalizedExpected = normalizeOutput(expectedOutput); + + expect(normalizedComputed).toEqual(normalizedExpected); + }); + }); +}); diff --git a/server/job/utils/seralizeHelper.js b/server/job/utils/seralizeHelper.js new file mode 100644 index 000000000..6ea69b5b8 --- /dev/null +++ b/server/job/utils/seralizeHelper.js @@ -0,0 +1,53 @@ +const fs = require("fs"); +const path = require("path"); +const { default: InstanceMgr } = require("../../utils/instanceMgr"); + +const fixtureDir = path.join(__dirname, "..", "test", "fixtures", "job-totals"); + +/** + * Custom serializer for Dinero.js objects. + * @param key + * @param value + * @returns {*} + */ +const serializeDinero = (key, value) => { + if (value && typeof value === "object" && typeof value.toObject === "function") { + return value.toObject(); + } + return value; +}; + +/** + * Capture a fixture for job totals. + * @param inputData + * @param outputData + */ +const captureFixture = (inputData, outputData) => { + if (!fs.existsSync(fixtureDir)) { + fs.mkdirSync(fixtureDir, { recursive: true }); + } + + const fileName = `${inputData.id}.json`; + const filePath = path.join(fixtureDir, fileName); + + const dataToSave = { + environment: InstanceMgr({ + imex: "ca", + rome: "us" + }), + meta: { + ro_number: inputData.ro_number, + updated_at: inputData.updated_at + }, + input: inputData, + output: outputData + }; + + // Save the file using our custom serializer. + fs.writeFileSync(filePath, JSON.stringify(dataToSave, serializeDinero, 2), "utf8"); +}; + +module.exports = { + captureFixture, + serializeDinero +};