From 3094da2fbe03fd8d5731836fa822d609dfccc84e Mon Sep 17 00:00:00 2001 From: Yemitan Isaiah Olurotimi Date: Tue, 3 May 2022 10:48:37 +0200 Subject: [PATCH] feat(application-config): Enforce uniqueness for uriPath of the submenu links. (#2567) * refactor(shield-469): validate application config for submenulink, throw error when there is a duplicate uripath * refactor(shield-469): add changeset * refactor(application-config): implement helper for validating submenulinks * refactor(application-config): rename validations-config file, use named exports * refactor(application-config): remove unnecessary hyphen * refactor(application-config): pass custom app object to vaalidator --- .changeset/bright-hornets-enjoy.md | 5 ++++ .../application-config/src/process-config.ts | 4 +-- .../application-config/src/transformers.ts | 3 +++ .../{validate-config.ts => validations.ts} | 17 +++++++++++-- .../test/json-schema.spec.js | 25 ++++++++++++++++++- .../test/load-config.spec.js | 2 +- 6 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 .changeset/bright-hornets-enjoy.md rename packages/application-config/src/{validate-config.ts => validations.ts} (75%) diff --git a/.changeset/bright-hornets-enjoy.md b/.changeset/bright-hornets-enjoy.md new file mode 100644 index 0000000000..b0f3516b46 --- /dev/null +++ b/.changeset/bright-hornets-enjoy.md @@ -0,0 +1,5 @@ +--- +'@commercetools-frontend/application-config': patch +--- + +Enforce uniqueness for `uriPath` of the submenu links. diff --git a/packages/application-config/src/process-config.ts b/packages/application-config/src/process-config.ts index 53fd3aaf05..88dfe1e9bc 100644 --- a/packages/application-config/src/process-config.ts +++ b/packages/application-config/src/process-config.ts @@ -10,7 +10,7 @@ import type { import fs from 'fs'; import omitEmpty from 'omit-empty-es'; import loadConfig from './load-config'; -import validate from './validate-config'; +import { validateConfig } from './validations'; import substituteVariablePlaceholders from './substitute-variable-placeholders'; import { mapCloudIdentifierToApiUrl, @@ -54,7 +54,7 @@ const processConfig = ({ if (cachedConfig && !disableCache) return cachedConfig; const rawConfig = loadConfig(applicationPath); - validate(rawConfig); + validateConfig(rawConfig); const appConfig = substituteVariablePlaceholders( rawConfig, diff --git a/packages/application-config/src/transformers.ts b/packages/application-config/src/transformers.ts index f73f757790..9727b12e54 100644 --- a/packages/application-config/src/transformers.ts +++ b/packages/application-config/src/transformers.ts @@ -1,6 +1,7 @@ import type { JSONSchemaForCustomApplicationConfigurationFiles } from './schema'; import type { CustomApplicationData } from './types'; import { entryPointUriPathToResourceAccesses } from './formatters'; +import { validateSubmenuLinks } from './validations'; // The `uriPath` of each submenu link is supposed to be defined relative // to the `entryPointUriPath`. Computing the full path is done internally to keep @@ -24,6 +25,8 @@ function transformCustomApplicationConfigToData( appConfig.entryPointUriPath ); + validateSubmenuLinks(appConfig); + return { id: appConfig.env.production.applicationId, name: appConfig.name, diff --git a/packages/application-config/src/validate-config.ts b/packages/application-config/src/validations.ts similarity index 75% rename from packages/application-config/src/validate-config.ts rename to packages/application-config/src/validations.ts index 6bb4d0147a..95b9a1556b 100644 --- a/packages/application-config/src/validate-config.ts +++ b/packages/application-config/src/validations.ts @@ -9,6 +9,7 @@ type ErrorAdditionalProperty = ErrorObject< type ErrorEnum = ErrorObject<'enum', { allowedValues: string[] }>; const ajv = new Ajv({ strict: true, useDefaults: true }); + const validate = ajv.compile(schemaJson); @@ -36,7 +37,7 @@ const printErrors = (errors?: ErrorObject[] | null) => { .join('\n'); }; -const validateConfig = ( +export const validateConfig = ( config: JSONSchemaForCustomApplicationConfigurationFiles ): void => { const valid = validate(config); @@ -45,4 +46,16 @@ const validateConfig = ( } }; -export default validateConfig; +export const validateSubmenuLinks = ( + config: JSONSchemaForCustomApplicationConfigurationFiles +) => { + const uriPathSet = new Set(); + config.submenuLinks.forEach(({ uriPath }) => { + if (uriPathSet.has(uriPath)) { + throw new Error( + 'Duplicate URI path. Every submenu link must have a unique URI path value' + ); + } + uriPathSet.add(uriPath); + }); +}; diff --git a/packages/application-config/test/json-schema.spec.js b/packages/application-config/test/json-schema.spec.js index 1b5effaaf4..21720a0e8a 100644 --- a/packages/application-config/test/json-schema.spec.js +++ b/packages/application-config/test/json-schema.spec.js @@ -1,4 +1,4 @@ -import validateConfig from '../src/validate-config'; +import { validateConfig, validateSubmenuLinks } from '../src/validations'; import fixtureConfigSimple from './fixtures/config-simple.json'; import fixtureConfigFull from './fixtures/config-full.json'; import fixtureConfigOidc from './fixtures/config-oidc.json'; @@ -173,4 +173,27 @@ describe('invalid configurations', () => { `" must have required property 'mainMenuLink'"` ); }); + it('should validate that "uriPath" for "submenuLinks" is unique', () => { + expect(() => + validateSubmenuLinks({ + ...fixtureConfigSimple, + submenuLinks: [ + { + uriPath: 'custom-app/avengers', + defaultLabel: 'avengers', + permissions: [], + labelAllLocales: [], + }, + { + uriPath: 'custom-app/avengers', + defaultLabel: 'justice-league', + permissions: [], + labelAllLocales: [], + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Duplicate URI path. Every submenu link must have a unique URI path value"` + ); + }); }); diff --git a/packages/application-config/test/load-config.spec.js b/packages/application-config/test/load-config.spec.js index 139a9ed1d0..df7a97afab 100644 --- a/packages/application-config/test/load-config.spec.js +++ b/packages/application-config/test/load-config.spec.js @@ -1,6 +1,6 @@ import path from 'path'; import loadConfig from '../src/load-config'; -import validateConfig from '../src/validate-config'; +import { validateConfig } from '../src/validations'; describe.each` extension | fixtureApp