From 320ad065d040d5aa53c744e4937d53a10a93de5f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:19:44 -0400 Subject: [PATCH 1/8] feature/IO-2769-Job-Totals-testing: Setup testing method for job totals --- .gitignore | 2 +- Dockerfile | 3 +- server/job/job-totals-USA.js | 24 ++++++++--- server/job/job-totals.js | 17 +++++++- server/job/test/job-totals.test.js | 68 ++++++++++++++++++++++++++++++ server/job/utils/seralizeHelper.js | 47 +++++++++++++++++++++ 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 server/job/test/job-totals.test.js create mode 100644 server/job/utils/seralizeHelper.js 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..2b92702bb 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) { + captureFixture(inputForTotals, 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..92edcd238 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) { + captureFixture(inputForTotals, 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..b0a30a29f --- /dev/null +++ b/server/job/test/job-totals.test.js @@ -0,0 +1,68 @@ +import fs from "fs"; +import path from "path"; +import { describe, it, expect } from "vitest"; +import { TotalsServerSide } from "../job-totals"; // adjust the path as needed +import Dinero from "dinero.js"; + +// A custom replacer to normalize Dinero objects +function dineroReplacer(key, value) { + if (value && typeof value === "object" && typeof value.toObject === "function") { + return value.toObject(); + } + return value; +} + +// Normalization function to convert any Dinero instances to plain objects +function normalizeOutput(obj) { + return JSON.parse(JSON.stringify(obj, dineroReplacer)); +} + +describe("TotalsServerSide fixture tests", () => { + // Define the fixture directory. + // For example, if this test file is at /server/job/test/, + // then the fixtures are in /server/job/test/fixtures/job-totals/ + const fixturesDir = path.join(__dirname, "fixtures", "job-totals"); + + // Read all fixture JSON files from the fixture directory. + const fixtureFiles = fs.readdirSync(fixturesDir).filter((f) => f.endsWith(".json")); + + // Create a dummy client. If your TotalsServerSide uses client.request, + // make sure these paths are either not triggered or stubbed accordingly. + const dummyClient = { + request: async () => { + // Return an empty object (or any other value that makes sense for your tests). + return {}; + } + }; + + // Create a dummy response object. TotalsServerSide might only use it to send error statuses. + 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 { input, output: expectedOutput } = fixtureData; + + const req = { + body: { + job: input, + client: dummyClient + }, + user: {} + }; + + // Call the TotalsServerSide function with the fixture input. + const computedOutput = await TotalsServerSide(req, dummyRes); + + // Normalize both computed and expected outputs so that any Dinero objects are replaced with their plain representation. + 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..b95779b7b --- /dev/null +++ b/server/job/utils/seralizeHelper.js @@ -0,0 +1,47 @@ +const fs = require("fs"); +const path = require("path"); + +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 the value has a toObject method (as in Dinero instances), use it. + if (value && typeof value === "object" && typeof value.toObject === "function") { + return value.toObject(); + } + return value; +}; + +/** + * Capture a fixture for job totals. + * @param job + * @param inputData + * @param outputData + */ +const captureFixture = (job, inputData, outputData) => { + if (!fs.existsSync(fixtureDir)) { + fs.mkdirSync(fixtureDir, { recursive: true }); + } + + // Use job.id to label the file. + const fileName = `${job.id}.json`; + const filePath = path.join(fixtureDir, fileName); + + const dataToSave = { + input: inputData, + output: outputData + }; + + // Save the file using our custom serializer. + fs.writeFileSync(filePath, JSON.stringify(dataToSave, serializeDinero, 2), "utf8"); +}; + +module.exports = { + captureFixture, + serializeDinero +}; From add88659a4010144519c45e4484290493831e2cf Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:23:33 -0400 Subject: [PATCH 2/8] feature/IO-2769-Job-Totals-testing: Setup testing method for job totals --- server/job/test/job-totals.test.js | 37 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/server/job/test/job-totals.test.js b/server/job/test/job-totals.test.js index b0a30a29f..d615d169c 100644 --- a/server/job/test/job-totals.test.js +++ b/server/job/test/job-totals.test.js @@ -1,41 +1,44 @@ import fs from "fs"; import path from "path"; import { describe, it, expect } from "vitest"; -import { TotalsServerSide } from "../job-totals"; // adjust the path as needed -import Dinero from "dinero.js"; +import { TotalsServerSide } from "../job-totals"; -// A custom replacer to normalize Dinero objects -function dineroReplacer(key, value) { +/** + * This function is used to replace the values in the object with their + * @param key + * @param value + * @returns {*} + */ +const dineroReplacer = (key, value) => { if (value && typeof value === "object" && typeof value.toObject === "function") { return value.toObject(); } return value; -} +}; -// Normalization function to convert any Dinero instances to plain objects -function normalizeOutput(obj) { +/** + * 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", () => { - // Define the fixture directory. - // For example, if this test file is at /server/job/test/, - // then the fixtures are in /server/job/test/fixtures/job-totals/ const fixturesDir = path.join(__dirname, "fixtures", "job-totals"); - // Read all fixture JSON files from the fixture directory. const fixtureFiles = fs.readdirSync(fixturesDir).filter((f) => f.endsWith(".json")); - // Create a dummy client. If your TotalsServerSide uses client.request, - // make sure these paths are either not triggered or stubbed accordingly. const dummyClient = { request: async () => { - // Return an empty object (or any other value that makes sense for your tests). return {}; } }; - // Create a dummy response object. TotalsServerSide might only use it to send error statuses. const dummyRes = { status: () => ({ send: () => {} }) }; @@ -55,10 +58,8 @@ describe("TotalsServerSide fixture tests", () => { user: {} }; - // Call the TotalsServerSide function with the fixture input. const computedOutput = await TotalsServerSide(req, dummyRes); - // Normalize both computed and expected outputs so that any Dinero objects are replaced with their plain representation. const normalizedComputed = normalizeOutput(computedOutput); const normalizedExpected = normalizeOutput(expectedOutput); From 27a3932c084eb7e4d5749f1f57314b54602b79b2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:43:19 -0400 Subject: [PATCH 3/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/job-totals-USA.js | 9 ++++++++- server/job/job-totals.js | 10 +++++++++- server/job/test/job-totals.test.js | 8 +++++--- server/job/utils/seralizeHelper.js | 11 ++++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 2b92702bb..cf22c3adc 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -39,7 +39,14 @@ exports.totalsSsu = async function (req, res) { // Capture fixture data (input and output), using job.id for the filename. if (process.env?.SAVE_TOTALS_DATA) { - captureFixture(inputForTotals, inputForTotals, newTotals); + captureFixture( + inputForTotals, + newTotals, + InstanceMgr({ + imex: "ca", + rome: "us" + }) + ); } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 92edcd238..f9c2ec8fa 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -2,6 +2,7 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); const { captureFixture } = require("./utils/seralizeHelper"); +const { default: InstanceMgr } = require("../utils/instanceMgr"); //****************************************************** */ //****************************************************** */ @@ -39,7 +40,14 @@ exports.totalsSsu = async function (req, res) { // Capture fixture data (input and output), using job.id for the filename. if (process.env?.SAVE_TOTALS_DATA) { - captureFixture(inputForTotals, inputForTotals, newTotals); + captureFixture( + inputForTotals, + newTotals, + InstanceMgr({ + imex: "ca", + rome: "us" + }) + ); } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { diff --git a/server/job/test/job-totals.test.js b/server/job/test/job-totals.test.js index d615d169c..6f5af805b 100644 --- a/server/job/test/job-totals.test.js +++ b/server/job/test/job-totals.test.js @@ -1,7 +1,8 @@ import fs from "fs"; import path from "path"; import { describe, it, expect } from "vitest"; -import { TotalsServerSide } from "../job-totals"; +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 @@ -48,7 +49,7 @@ describe("TotalsServerSide fixture tests", () => { const fixturePath = path.join(fixturesDir, file); const fixtureData = JSON.parse(fs.readFileSync(fixturePath, "utf8")); - const { input, output: expectedOutput } = fixtureData; + const { environment, input, output: expectedOutput } = fixtureData; const req = { body: { @@ -58,7 +59,8 @@ describe("TotalsServerSide fixture tests", () => { user: {} }; - const computedOutput = await TotalsServerSide(req, dummyRes); + const computedOutput = + environment === "us" ? await TotalsServerSideUS(req, dummyRes) : await TotalsServerSideCA(req, dummyRes); const normalizedComputed = normalizeOutput(computedOutput); const normalizedExpected = normalizeOutput(expectedOutput); diff --git a/server/job/utils/seralizeHelper.js b/server/job/utils/seralizeHelper.js index b95779b7b..3d7aae03c 100644 --- a/server/job/utils/seralizeHelper.js +++ b/server/job/utils/seralizeHelper.js @@ -19,20 +19,25 @@ const serializeDinero = (key, value) => { /** * Capture a fixture for job totals. - * @param job * @param inputData * @param outputData + * @param environment */ -const captureFixture = (job, inputData, outputData) => { +const captureFixture = (inputData, outputData, environment) => { if (!fs.existsSync(fixtureDir)) { fs.mkdirSync(fixtureDir, { recursive: true }); } // Use job.id to label the file. - const fileName = `${job.id}.json`; + const fileName = `${inputData.id}.json`; const filePath = path.join(fixtureDir, fileName); const dataToSave = { + environment: environment, + meta: { + ro_number: inputData.ro_number, + updated_at: inputData.updated_at + }, input: inputData, output: outputData }; From 8323fa669685528a9e7e90eff5a9a550cc34e5a7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:45:45 -0400 Subject: [PATCH 4/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/utils/seralizeHelper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/job/utils/seralizeHelper.js b/server/job/utils/seralizeHelper.js index 3d7aae03c..d526af30f 100644 --- a/server/job/utils/seralizeHelper.js +++ b/server/job/utils/seralizeHelper.js @@ -10,7 +10,6 @@ const fixtureDir = path.join(__dirname, "..", "test", "fixtures", "job-totals"); * @returns {*} */ const serializeDinero = (key, value) => { - // If the value has a toObject method (as in Dinero instances), use it. if (value && typeof value === "object" && typeof value.toObject === "function") { return value.toObject(); } @@ -28,7 +27,6 @@ const captureFixture = (inputData, outputData, environment) => { fs.mkdirSync(fixtureDir, { recursive: true }); } - // Use job.id to label the file. const fileName = `${inputData.id}.json`; const filePath = path.join(fixtureDir, fileName); From 51c2d3351a17cd3c6d4b16a4f1f5ac82493a555f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:47:38 -0400 Subject: [PATCH 5/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/job-totals-USA.js | 9 +-------- server/job/job-totals.js | 9 +-------- server/job/utils/seralizeHelper.js | 9 ++++++--- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index cf22c3adc..f125217f5 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -39,14 +39,7 @@ exports.totalsSsu = async function (req, res) { // Capture fixture data (input and output), using job.id for the filename. if (process.env?.SAVE_TOTALS_DATA) { - captureFixture( - inputForTotals, - newTotals, - InstanceMgr({ - imex: "ca", - rome: "us" - }) - ); + captureFixture(inputForTotals, newTotals); } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { diff --git a/server/job/job-totals.js b/server/job/job-totals.js index f9c2ec8fa..2984573a6 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -40,14 +40,7 @@ exports.totalsSsu = async function (req, res) { // Capture fixture data (input and output), using job.id for the filename. if (process.env?.SAVE_TOTALS_DATA) { - captureFixture( - inputForTotals, - newTotals, - InstanceMgr({ - imex: "ca", - rome: "us" - }) - ); + captureFixture(inputForTotals, newTotals); } const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { diff --git a/server/job/utils/seralizeHelper.js b/server/job/utils/seralizeHelper.js index d526af30f..6ea69b5b8 100644 --- a/server/job/utils/seralizeHelper.js +++ b/server/job/utils/seralizeHelper.js @@ -1,5 +1,6 @@ const fs = require("fs"); const path = require("path"); +const { default: InstanceMgr } = require("../../utils/instanceMgr"); const fixtureDir = path.join(__dirname, "..", "test", "fixtures", "job-totals"); @@ -20,9 +21,8 @@ const serializeDinero = (key, value) => { * Capture a fixture for job totals. * @param inputData * @param outputData - * @param environment */ -const captureFixture = (inputData, outputData, environment) => { +const captureFixture = (inputData, outputData) => { if (!fs.existsSync(fixtureDir)) { fs.mkdirSync(fixtureDir, { recursive: true }); } @@ -31,7 +31,10 @@ const captureFixture = (inputData, outputData, environment) => { const filePath = path.join(fixtureDir, fileName); const dataToSave = { - environment: environment, + environment: InstanceMgr({ + imex: "ca", + rome: "us" + }), meta: { ro_number: inputData.ro_number, updated_at: inputData.updated_at From 7ddec0bb0fa75fbbe1e0d7770a519ce84588f958 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:49:21 -0400 Subject: [PATCH 6/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/job-totals.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 2984573a6..278fa9bfa 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -2,7 +2,6 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); const { captureFixture } = require("./utils/seralizeHelper"); -const { default: InstanceMgr } = require("../utils/instanceMgr"); //****************************************************** */ //****************************************************** */ From 14630378784a917b9198a26b60273197b0b6f3d2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:54:53 -0400 Subject: [PATCH 7/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/test/job-totals.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/job/test/job-totals.test.js b/server/job/test/job-totals.test.js index 6f5af805b..059b0bb1e 100644 --- a/server/job/test/job-totals.test.js +++ b/server/job/test/job-totals.test.js @@ -5,7 +5,7 @@ import { TotalsServerSide as TotalsServerSideCA } from "../job-totals"; // Canad import { TotalsServerSide as TotalsServerSideUS } from "../job-totals-USA"; // US version (rome) /** - * This function is used to replace the values in the object with their + * This function is used to replace the values in the object with their toObject() representation. * @param key * @param value * @returns {*} From f777d26cc19c93f641f15f2d010695453725a1ad Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 14:15:30 -0400 Subject: [PATCH 8/8] feature/IO-2769-Job-Totals-testing: Allow for both american and canadian capture + tests --- server/job/job-totals-USA.js | 2 +- server/job/job-totals.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index f125217f5..8cde6ebb3 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -38,7 +38,7 @@ exports.totalsSsu = async function (req, res) { 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) { + if (process.env?.SAVE_TOTALS_DATA === "true") { captureFixture(inputForTotals, newTotals); } diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 278fa9bfa..3aba47d1f 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -38,7 +38,7 @@ exports.totalsSsu = async function (req, res) { 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) { + if (process.env?.SAVE_TOTALS_DATA === "true") { captureFixture(inputForTotals, newTotals); }