From eb2e55adf92e0a5442ffbaf09cd6620c640152aa Mon Sep 17 00:00:00 2001 From: Simon Varney Date: Mon, 29 Jul 2024 17:00:19 +0200 Subject: [PATCH] Move last two title helpers from dist-api (#170) Add getSenderId and getTsCode title functions from distribution-api --- config/product-mapping.js | 1 + lib/titles.js | 30 +++++++-- test/unit/titles-get-sender-id-test.js | 28 ++++++++ test/unit/titles-get-ts-code-test.js | 78 +++++++++++++++++++++++ test/unit/titles-sanity-check-test.js | 88 ++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 test/unit/titles-get-sender-id-test.js create mode 100644 test/unit/titles-get-ts-code-test.js create mode 100644 test/unit/titles-sanity-check-test.js diff --git a/config/product-mapping.js b/config/product-mapping.js index a87e337..f761b34 100644 --- a/config/product-mapping.js +++ b/config/product-mapping.js @@ -586,6 +586,7 @@ const productConfig = { { title: "ht", alternativeTitles: [ "hudiksvalsstidning", "hudiksvallstidning" ], + complaintSenderId: "TBD", // needed for tests, but not used in production, will be filled in when needed tsCode: "0990", shortName: "", subDirectory: "", diff --git a/lib/titles.js b/lib/titles.js index 3e37259..0e22b80 100644 --- a/lib/titles.js +++ b/lib/titles.js @@ -51,6 +51,17 @@ function getAllTitles() { return realTitlesConfig.map((p) => p.title); } +function getSenderId(namespace, title) { + const product = getTitleConfig(title); + assert(product?.complaintSenderId, "No senderId for report complaint was found. Update product-mapping config"); + assert( + product.namespace === namespace, + `title ${title} not valid for namespace ${namespace}. Check product-mapping config` + ); + + return product?.complaintSenderId; +} + function getTitlesByNamespace(namespace) { return realTitlesConfig.filter((p) => p.namespace === namespace).map((p) => p.title); } @@ -59,6 +70,13 @@ function getTitleConfig(title) { return productMapping.find((p) => p.title === title || (p.alternativeTitles && p.alternativeTitles.includes(title))); } +function getTsCode(title) { + assert(title, "No title was provided"); + const product = getTitleConfig(title); + + return product?.tsCode; +} + function getDeliveryDays(namespace, title) { const product = getTitleConfig(title); assert(product?.deliveryDays, `No delivery days for title ${title} was found. Update product-mapping config`); @@ -147,17 +165,19 @@ export { alternativeTitleConfig, allTitlesConfig, deliveryDaysToNumberOfJobs, - numberOfJobsToJobOffsets, - postalDeliveryAllowed, getAllPrintTitles, + getAllTitles, getDeliveryDays, + getNamespaceByTitle, getPrintTitlesByNamespace, - getAllTitles, + getSenderId, getTitlesByNamespace, getTitleConfig, - getNamespaceByTitle, - productMapping, + getTsCode, + numberOfJobsToJobOffsets, + postalDeliveryAllowed, productConfig, + productMapping, realTitlesConfig, validNamespaces, }; diff --git a/test/unit/titles-get-sender-id-test.js b/test/unit/titles-get-sender-id-test.js new file mode 100644 index 0000000..0d77fdc --- /dev/null +++ b/test/unit/titles-get-sender-id-test.js @@ -0,0 +1,28 @@ +import { getSenderId, productMapping } from "../../lib/titles.js"; + +const allPrintProducts = productMapping.filter((p) => p.title && p.tsCode); + +describe("get complaints sender id", () => { + describe("titles with sender id", () => { + allPrintProducts + .filter((p) => Boolean(p.complaintSenderId)) + .forEach(({ namespace, title }) => { + it(`${namespace}/${title}`, () => { + getSenderId(namespace, title).should.eql(productMapping.find((pm) => pm.title === title).complaintSenderId); + }); + }); + }); + describe("titles without sender id", () => { + allPrintProducts + .filter((p) => !p.complaintSenderId) + .forEach(({ namespace, title }) => { + it(`${namespace}/${title}`, () => { + try { + getSenderId(namespace, title); + } catch (error) { + error.message.should.eql("No senderId for report complaint was found. Update product-mapping config"); + } + }); + }); + }); +}); diff --git a/test/unit/titles-get-ts-code-test.js b/test/unit/titles-get-ts-code-test.js new file mode 100644 index 0000000..c5cdb65 --- /dev/null +++ b/test/unit/titles-get-ts-code-test.js @@ -0,0 +1,78 @@ +import { getTsCode, productMapping } from "../../lib/titles.js"; + +const titleTsCodeMapping = [ + { + tsCode: "0550", + title: "expressen", + text: "Expressen", + singleTitle: false, + }, + { + tsCode: "0760", + title: "gt", + text: "GT", + singleTitle: false, + }, + { + tsCode: "1160", + title: "kvp", + text: "Kvällsposten", + singleTitle: false, + }, + { + tsCode: "0440", + title: "dn", + text: "DN", + singleTitle: false, + }, + { + tsCode: "0435", + title: "di", + text: "DI", + singleTitle: true, + }, + { + tsCode: "0990", + title: "ht", + text: "Hudiksvalls Tidning", + singleTitle: false, + }, + { + tsCode: "0990", + title: "hudiksvalsstidning", + expectedTitle: "ht", + text: "Hudiksvalls Tidning (wrong spelling)", + singleTitle: false, + }, +]; + +describe("get tsCode test", () => { + describe("get tsCode by title", () => { + for (const s of titleTsCodeMapping) { + describe(s.text, () => { + it(`should find tsCode ${s.tsCode} based on title ${s.title}`, () => { + const tsCode = getTsCode(s.title); + tsCode.should.eql(s.tsCode); + }); + }); + } + }); + + describe("get title by tsCode", () => { + for (const s of titleTsCodeMapping) { + describe(s.text, () => { + it(`should find title ${s.title} based on tsCode ${s.tsCode}`, () => { + const { title } = productMapping.find((pm) => pm.tsCode === s.tsCode); + title.should.eql(s.expectedTitle || s.title); + }); + }); + } + }); + + describe("get tsCode for invalid title", () => { + it("should not find a tsCode for some-title", () => { + const tsCode = getTsCode("some-title"); + Boolean(tsCode).should.eql(false); + }); + }); +}); diff --git a/test/unit/titles-sanity-check-test.js b/test/unit/titles-sanity-check-test.js new file mode 100644 index 0000000..7ec4803 --- /dev/null +++ b/test/unit/titles-sanity-check-test.js @@ -0,0 +1,88 @@ +import { productMapping, productConfig } from "../../lib/titles.js"; + +describe("sanity check productMapping", () => { + describe("check for duplicate productCode/type combinations", () => { + it("should not find any duplicate productCode/type combinations", () => { + const allProductCodeTypeCombos = productMapping + .filter((pm) => pm.productCode) + .map((pm) => pm.type + pm.productCode); + const thereAreDuplicates = hasDuplicates(allProductCodeTypeCombos); + thereAreDuplicates.should.eql(false); + }); + }); + + describe("check for duplicate titles", () => { + it("should not find any duplicate titles", () => { + const allTitles = productMapping.filter((pm) => pm.title).map((pm) => pm.title); + const thereAreDuplicates = hasDuplicates(allTitles); + thereAreDuplicates.should.eql(false); + }); + }); +}); + +describe("sanity check productConfig", () => { + const flattenedProductConfig = []; + Object.keys(productConfig).map((namespace) => { + flattenedProductConfig.push(...productConfig[namespace]); + }); + + describe("check for duplicate tsCodes", () => { + it("should not find any duplicate tsCodes", () => { + const allTsCodes = flattenedProductConfig.filter((pm) => pm.tsCode).map((pm) => pm.tsCode); + const thereAreDuplicates = hasDuplicates(allTsCodes); + if (thereAreDuplicates) reportDuplicates(flattenedProductConfig, "tsCode"); + thereAreDuplicates.should.eql(false); + }); + }); + + describe("check for duplicate titles", () => { + it("should not find any duplicate titles", () => { + const allTitles = flattenedProductConfig.filter((pm) => pm.title).map((pm) => pm.title); + const thereAreDuplicates = hasDuplicates(allTitles); + if (thereAreDuplicates) reportDuplicates(flattenedProductConfig, "title"); + thereAreDuplicates.should.eql(false); + }); + }); + + describe("check for duplicate short names", () => { + it("should not find any duplicate short names", () => { + const allShortNames = flattenedProductConfig.filter((pm) => pm.shortName).map((pm) => pm.shortName); + const thereAreDuplicates = hasDuplicates(allShortNames); + if (thereAreDuplicates) reportDuplicates(flattenedProductConfig, "shortName"); + thereAreDuplicates.should.eql(false); + }); + }); + + describe("check for duplicate subdirectories", () => { + it("should not find any duplicate subdirectories", () => { + const allSubDirectories = flattenedProductConfig.filter((pm) => pm.subDirectory).map((pm) => pm.subDirectory); + const thereAreDuplicates = hasDuplicates(allSubDirectories); + if (thereAreDuplicates) reportDuplicates(flattenedProductConfig, "subDirectory"); + thereAreDuplicates.should.eql(false); + }); + }); +}); + +function hasDuplicates(arr) { + return new Set(arr).size !== arr.length; +} + +function reportDuplicates(arr, property) { + const propertyMap = {}; + arr + .filter((o) => o[property]) + .map((o) => { + const thisProperty = o[property]; + if (!propertyMap[thisProperty]) propertyMap[thisProperty] = []; + propertyMap[thisProperty].push(o.title || o.shortName || o.productName); + }); + const foundMultiples = []; + Object.keys(propertyMap).forEach((p) => { + if (propertyMap[p].length > 1) { + const multiple = {}; + multiple[p] = propertyMap[p]; + foundMultiples.push(multiple); + } + }); + if (foundMultiples.length) console.log(foundMultiples); // eslint-disable-line no-console +}