From 320ad065d040d5aa53c744e4937d53a10a93de5f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 4 Apr 2025 13:19:44 -0400 Subject: [PATCH] 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 +};