diff --git a/actions/services/adminDirectService.js b/actions/services/adminDirectService.js index 0ea3fe38..94b14af8 100644 --- a/actions/services/adminDirectService.js +++ b/actions/services/adminDirectService.js @@ -1,14 +1,13 @@ import axios from "axios"; import { BASE_URL } from "../core/env"; -import { consoleLogger } from "../core/logger"; +import { logAndReturnResponse } from "./httpServiceUtils"; export const getNewAppeals = async (searchString) => { try { const res = await axios.get(BASE_URL + "/api/admin/getnewappeals_api"); return res.data; } catch (error) { - consoleLogger(error); - return error.response; + return logAndReturnResponse(error); } }; @@ -35,10 +34,7 @@ export const getNewAppealsPage = async ( .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getNewDocumentsPaged = async ( @@ -50,22 +46,6 @@ export const getNewDocumentsPaged = async ( documentOrigin, selectedWeeks ) => { - console.log( - "/api/admin/getlatestdocuments_api?pageNumber=" + - pageNumber + - "&orderby=" + - orderBy + - "&fieldSort=" + - fieldSort + - "&showNumberOfRecords=" + - showNumberOfRecords + - "&documentType=" + - documentType + - "&numberWeeks=" + - selectedWeeks + - "&documentOrigin=" + - documentOrigin - ); return axios .get( "/api/admin/getlatestdocuments_api?pageNumber=" + @@ -86,8 +66,5 @@ export const getNewDocumentsPaged = async ( .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; diff --git a/actions/services/caseDirectService.js b/actions/services/caseDirectService.js index 5e1c5f8b..ed9c2291 100644 --- a/actions/services/caseDirectService.js +++ b/actions/services/caseDirectService.js @@ -1,6 +1,7 @@ import axios from "axios"; import { BASE_URL } from "../core/env"; import { consoleLogger } from "../core/logger"; +import { logAndReturnResponse } from "./httpServiceUtils"; export const getCaseMessage = (searchString) => { return axios @@ -8,10 +9,7 @@ export const getCaseMessage = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getIncidentbyID = (searchString) => { @@ -24,10 +22,7 @@ export const getIncidentbyID = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getIsPublishedbyID = (searchString) => { @@ -38,10 +33,7 @@ export const getIsPublishedbyID = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getPartSavedAppeal = (searchString) => { @@ -54,10 +46,7 @@ export const getPartSavedAppeal = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getSIPSEvents = async (caseid) => { @@ -66,10 +55,7 @@ export const getSIPSEvents = async (caseid) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getSIPSMedia = async (caseid) => { @@ -78,10 +64,7 @@ export const getSIPSMedia = async (caseid) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getAppealID = ( @@ -342,7 +325,6 @@ export const getPortalModuleDetails = async (appealType, caseReference) => { const res = await axios(config); return res.data; } catch (error) { - console.log("error case ref:", caseReference); consoleLogger(error); } }; diff --git a/actions/services/documentDirectService.js b/actions/services/documentDirectService.js index 6d518ea2..0b185789 100644 --- a/actions/services/documentDirectService.js +++ b/actions/services/documentDirectService.js @@ -50,14 +50,6 @@ export const getRepsFromBlobProxy = async (containerName) => { }; export const getAwaitingSubmissionFromBlobProxy = async (containerName) => { - console.log( - "///////////////////////getAwaitingSubmissionFromBlobProxy: " + - containerName, - "/api/file/getawaitingsubmissionfromblobproxy?container=" + - containerName, - "\n///////////////////////\n" - ); - try { const res = await axios.get( BASE_URL + @@ -163,7 +155,6 @@ export const uploadSingleFile = async (filesObj, containerID, casefolderID) => { formData.append("casefolderID", casefolderID); for (let i = 0; i < files.length; i++) { - console.log(files.length); formData.append(files[i].name, files[i]); } @@ -219,8 +210,6 @@ export const generateRepPDF = async ( casefolderID, options = {} ) => { - console.log("generateRepPDF function: " + formValues); - var queryUrl = "/api/file/generatepdf" + (options.download ? "?download=true" : ""); diff --git a/actions/services/httpServiceUtils.js b/actions/services/httpServiceUtils.js new file mode 100644 index 00000000..d0d26e31 --- /dev/null +++ b/actions/services/httpServiceUtils.js @@ -0,0 +1,19 @@ +import { consoleLogger } from "../core/logger"; + +export const buildEmptyValueErrorResponse = (error) => { + return { + value: [], + errorCode: error.response.status, + errorMsg: error.response.statusText + }; +}; + +export const logAndReturnResponse = (error) => { + consoleLogger(error); + return error.response; +}; + +export const logAndReturnEmptyValueErrorResponse = (error) => { + consoleLogger(error); + return buildEmptyValueErrorResponse(error); +}; diff --git a/actions/services/portalDirectService.js b/actions/services/portalDirectService.js index c0577505..5645af51 100644 --- a/actions/services/portalDirectService.js +++ b/actions/services/portalDirectService.js @@ -237,7 +237,6 @@ export const sendCaseCompleteMessage = async ( return axios(config) .then((res) => { - console.log("sendCaseCompleteMessage: ", res.data); return res.data; }) .catch((error) => { diff --git a/actions/services/referenceDataDirectService.js b/actions/services/referenceDataDirectService.js index 07e558cc..406c8103 100644 --- a/actions/services/referenceDataDirectService.js +++ b/actions/services/referenceDataDirectService.js @@ -1,53 +1,27 @@ import axios from "axios"; import { BASE_URL } from "../core/env"; import { consoleLogger } from "../core/logger"; +import { logAndReturnEmptyValueErrorResponse } from "./httpServiceUtils"; export const getAppealsTypes = () => { return axios .get(BASE_URL + "/api/endpoint/getappealtypes_api") .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getProjectTypes = () => { return axios .get(BASE_URL + "/api/endpoint/getprojecttypes_api") .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getAppealsTypesForNewAppeal = () => { return axios .get(BASE_URL + "/api/endpoint/getappealtypesfornewappeal_api") .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getLPA = () => { @@ -56,16 +30,7 @@ export const getLPA = () => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getFormData = (whichForm) => { diff --git a/actions/services/searchDirectService.js b/actions/services/searchDirectService.js index 5b364c71..0037ea98 100644 --- a/actions/services/searchDirectService.js +++ b/actions/services/searchDirectService.js @@ -1,6 +1,10 @@ import axios from "axios"; import { BASE_URL } from "../core/env"; import { consoleLogger } from "../core/logger"; +import { + logAndReturnResponse, + logAndReturnEmptyValueErrorResponse +} from "./httpServiceUtils"; export const getBasicSearch = (searchString) => { return axios @@ -12,10 +16,7 @@ export const getBasicSearch = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getBasicDNSURLSearch = (searchString) => { @@ -28,10 +29,7 @@ export const getBasicDNSURLSearch = (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getAddressSearchPaged = ( @@ -57,10 +55,7 @@ export const getAddressSearchPaged = ( .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getBasicSearchPaged = ( @@ -86,10 +81,7 @@ export const getBasicSearchPaged = ( .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getDNSCoords = () => { @@ -98,36 +90,21 @@ export const getDNSCoords = () => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getDNSList = (searchString) => { return axios .get(BASE_URL + "/api/endpoint/getdnslist_api") .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getBasicDNSSearch = (searchString) => { return axios .get(BASE_URL + "/api/endpoint/getbasicdnssearch_api") .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getBasicDNSSearchPaged = ( @@ -148,10 +125,7 @@ export const getBasicDNSSearchPaged = ( showNumberOfRecords ) .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - return error.response; - }); + .catch(logAndReturnResponse); }; export const getAdvancedSearch = (searchString) => { @@ -162,16 +136,7 @@ export const getAdvancedSearch = (searchString) => { JSON.stringify(searchString) ) .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getAdvancedSearchPaged = ( @@ -195,16 +160,7 @@ export const getAdvancedSearchPaged = ( showNumberOfRecords ) .then((res) => res.data) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; export const getBasicSearchDetails = async ( @@ -247,7 +203,7 @@ export const getBasicPartSavedDetails = ( ) .then((res) => res.data) .catch((error) => { - console.log("this error", error); + consoleLogger(error); }); }; @@ -259,8 +215,6 @@ export const getBasicSearchDetailsPaged = async ( ) => { if (!incidentIDs || incidentIDs.length === 0) return []; - console.log("=======", primaryIdAttribute); - const filter = incidentIDs .map((id) => { const suffix = @@ -276,10 +230,6 @@ export const getBasicSearchDetailsPaged = async ( filter )}`; - console.log( - `Fetching ${incidentIDs.length} incidents for appeal type: ${appealTypeName}` - ); - try { const res = await axios.get(url); return res.data.value || []; @@ -372,13 +322,5 @@ export const getAddressSearch = async (searchString) => { .then((res) => { return res.data; }) - .catch((error) => { - consoleLogger(error); - let ErrResponse = { - value: [], - errorCode: error.response.status, - errorMsg: error.response.statusText - }; - return ErrResponse; - }); + .catch(logAndReturnEmptyValueErrorResponse); }; diff --git a/tests/phase6/service-behaviour.test.cjs b/tests/phase6/service-behaviour.test.cjs new file mode 100644 index 00000000..e130c93f --- /dev/null +++ b/tests/phase6/service-behaviour.test.cjs @@ -0,0 +1,309 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const assert = require("assert"); + +const rootDir = path.resolve(__dirname, "..", ".."); +const servicesDir = path.join(rootDir, "actions", "services"); + +const createAxiosMock = () => { + const axios = (config) => axios.request(config); + + axios.calls = []; + axios.requestHandler = async () => { + throw new Error("No axios.request handler configured"); + }; + axios.getHandler = async () => { + throw new Error("No axios.get handler configured"); + }; + axios.postHandler = async () => { + throw new Error("No axios.post handler configured"); + }; + + axios.request = (config) => { + axios.calls.push({ type: "request", config }); + return axios.requestHandler(config); + }; + + axios.get = (url, config) => { + axios.calls.push({ type: "get", url, config }); + return axios.getHandler(url, config); + }; + + axios.post = (url, data, config) => { + axios.calls.push({ type: "post", url, data, config }); + return axios.postHandler(url, data, config); + }; + + return axios; +}; + +const createLoggerMock = () => { + const calls = []; + const consoleLogger = (error) => { + calls.push(error); + }; + return { consoleLogger, calls }; +}; + +const createAxiosError = (status = 500, statusText = "Server Error") => { + return { + response: { + status, + statusText, + data: {} + }, + config: { + url: "/mock-url" + } + }; +}; + +const loadServiceModule = (fileName, injected = {}) => { + const filePath = path.join(servicesDir, fileName); + let source = fs.readFileSync(filePath, "utf8"); + + source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); + + const exportNames = Array.from( + source.matchAll(/export const\s+(\w+)\s*=/g) + ).map((match) => match[1]); + + source = source.replace(/export const\s+/g, "const "); + source += `\nmodule.exports = { ${exportNames.join(", ")} };\n`; + + const context = { + module: { exports: {} }, + exports: {}, + require, + URLSearchParams, + encodeURIComponent, + FormData: global.FormData, + console: { + log: () => {}, + info: () => {}, + warn: () => {}, + error: () => {} + }, + ...injected + }; + + vm.runInNewContext(source, context, { filename: filePath }); + return context.module.exports; +}; + +const tests = []; + +const test = (name, fn) => { + tests.push({ name, fn }); +}; + +const normalize = (value) => JSON.parse(JSON.stringify(value)); + +test("search/getBasicSearch returns res.data on success", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + + axios.getHandler = async () => ({ data: { value: ["ok"] } }); + + const search = loadServiceModule("searchDirectService.js", { + axios, + BASE_URL: "http://example.local", + consoleLogger: logger.consoleLogger, + ...helpers + }); + + const result = await search.getBasicSearch("abc"); + + assert.deepStrictEqual(normalize(result), { value: ["ok"] }); + assert.strictEqual( + axios.calls[0].url, + "http://example.local/api/endpoint/getbasicsearch_api?searchString=abc" + ); +}); + +test("search/getBasicSearch returns error.response on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + + const error = createAxiosError(503, "Unavailable"); + axios.getHandler = async () => Promise.reject(error); + + const search = loadServiceModule("searchDirectService.js", { + axios, + BASE_URL: "", + consoleLogger: logger.consoleLogger, + ...helpers + }); + + const result = await search.getBasicSearch("abc"); + + assert.strictEqual(result, error.response); + assert.strictEqual(logger.calls.length, 1); +}); + +test("search/getAdvancedSearch preserves ErrResponse shape on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + + axios.getHandler = async () => + Promise.reject(createAxiosError(400, "Bad Request")); + + const search = loadServiceModule("searchDirectService.js", { + axios, + BASE_URL: "", + consoleLogger: logger.consoleLogger, + ...helpers + }); + + const result = await search.getAdvancedSearch({ foo: "bar" }); + + assert.deepStrictEqual(normalize(result), { + value: [], + errorCode: 400, + errorMsg: "Bad Request" + }); + assert.strictEqual(logger.calls.length, 1); +}); + +test("reference/getAppealsTypes preserves ErrResponse shape on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + + axios.getHandler = async () => + Promise.reject(createAxiosError(404, "Not Found")); + + const reference = loadServiceModule("referenceDataDirectService.js", { + axios, + BASE_URL: "", + consoleLogger: logger.consoleLogger, + ...helpers + }); + + const result = await reference.getAppealsTypes(); + + assert.deepStrictEqual(normalize(result), { + value: [], + errorCode: 404, + errorMsg: "Not Found" + }); + assert.strictEqual(logger.calls.length, 1); +}); + +test("case/getCaseMessage returns error.response on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + const error = createAxiosError(500, "Error"); + + axios.getHandler = async () => Promise.reject(error); + + const caseService = loadServiceModule("caseDirectService.js", { + axios, + BASE_URL: "", + consoleLogger: logger.consoleLogger, + ...helpers + }); + + const result = await caseService.getCaseMessage("x"); + + assert.strictEqual(result, error.response); + assert.strictEqual(logger.calls.length, 1); +}); + +test("admin/getNewAppealsPage returns res.data on success", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + + axios.getHandler = async () => ({ data: { value: [1, 2] } }); + + const admin = loadServiceModule("adminDirectService.js", { + axios, + BASE_URL: "", + ...helpers + }); + + const result = await admin.getNewAppealsPage("q", 1, "a", "b", 20); + + assert.deepStrictEqual(normalize(result), { value: [1, 2] }); + assert.strictEqual(logger.calls.length, 0); +}); + +test("admin/getNewAppealsPage returns error.response on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + const error = createAxiosError(502, "Bad Gateway"); + + axios.getHandler = async () => Promise.reject(error); + + const admin = loadServiceModule("adminDirectService.js", { + axios, + BASE_URL: "", + ...helpers + }); + + const result = await admin.getNewAppealsPage("q", 1, "a", "b", 20); + + assert.strictEqual(result, error.response); + assert.strictEqual(logger.calls.length, 1); +}); + +test("admin/getNewAppeals returns error.response on failure", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const helpers = loadServiceModule("httpServiceUtils.js", { + consoleLogger: logger.consoleLogger + }); + const error = createAxiosError(401, "Unauthorized"); + + axios.getHandler = async () => Promise.reject(error); + + const admin = loadServiceModule("adminDirectService.js", { + axios, + BASE_URL: "", + ...helpers + }); + + const result = await admin.getNewAppeals("ignored"); + + assert.strictEqual(result, error.response); + assert.strictEqual(logger.calls.length, 1); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 6 behavioural tests passed (${passed}/${tests.length}).` + ); +}; + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/tests/phase6/service-parity.test.cjs b/tests/phase6/service-parity.test.cjs new file mode 100644 index 00000000..5cef6fd5 --- /dev/null +++ b/tests/phase6/service-parity.test.cjs @@ -0,0 +1,101 @@ +const fs = require("fs"); +const path = require("path"); +const assert = require("assert"); + +const rootDir = path.resolve(__dirname, "..", ".."); +const servicesDir = path.join(rootDir, "actions", "services"); + +const groupedServiceFiles = [ + "searchService.js", + "referenceDataService.js", + "documentService.js", + "portalService.js", + "accountService.js", + "caseService.js", + "adminService.js", + "integrationService.js", + "notifyService.js" +]; + +const expectedIndexReExports = [ + "./searchService", + "./caseService", + "./accountService", + "./portalService", + "./documentService", + "./referenceDataService", + "./notifyService", + "./adminService", + "./integrationService" +]; + +const parseNamedList = (block) => { + return block + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + .map((item) => item.replace(/\s+/g, " ")); +}; + +const parseGroupedServiceFile = (filePath) => { + const source = fs.readFileSync(filePath, "utf8"); + + const importMatch = source.match( + /import\s*\{([\s\S]*?)\}\s*from\s*"(\.\/[^"]+)";/ + ); + assert( + importMatch, + `Unable to parse import block in ${path.basename(filePath)}` + ); + + const exportMatch = source.match(/export\s*\{([\s\S]*?)\};/); + assert( + exportMatch, + `Unable to parse export block in ${path.basename(filePath)}` + ); + + return { + directModulePath: importMatch[2], + importedNames: parseNamedList(importMatch[1]).sort(), + exportedNames: parseNamedList(exportMatch[1]).sort() + }; +}; + +const verifyGroupedServicesParity = () => { + groupedServiceFiles.forEach((groupedFile) => { + const groupedPath = path.join(servicesDir, groupedFile); + const parsed = parseGroupedServiceFile(groupedPath); + + assert( + parsed.directModulePath.endsWith("DirectService"), + `${groupedFile} does not import a direct service module` + ); + + assert.deepStrictEqual( + parsed.importedNames, + parsed.exportedNames, + `${groupedFile} import/export names are not in parity` + ); + }); +}; + +const verifyServicesIndexStability = () => { + const source = fs.readFileSync(path.join(servicesDir, "index.js"), "utf8"); + const matches = Array.from( + source.matchAll(/export \* from "([^"]+)";/g) + ).map((match) => match[1]); + + assert.deepStrictEqual( + matches, + expectedIndexReExports, + "actions/services/index.js re-export list has changed unexpectedly" + ); +}; + +const run = () => { + verifyGroupedServicesParity(); + verifyServicesIndexStability(); + console.log("Phase 6 parity tests passed."); +}; + +run();