diff --git a/api/auth/index.ts b/api/auth/index.ts index 87c87e371..e70937b81 100644 --- a/api/auth/index.ts +++ b/api/auth/index.ts @@ -1,6 +1,5 @@ import { AUTH, AuthOptions, xuiNode } from '@hmcts/rpx-xui-node-lib'; import { NextFunction, Request, Response } from 'express'; -import { EnhancedRequest } from '../models/enhanced-request.interface'; import { getConfigValue, showFeature } from '../configuration'; import { COOKIE_TOKEN, @@ -13,19 +12,20 @@ import { MICROSERVICE, NOW, OAUTH_CALLBACK_URL, + REDISCLOUD_URL, REDIS_KEY_PREFIX, REDIS_TTL, - REDISCLOUD_URL, S2S_SECRET, - SERVICE_S2S_PATH, + SERVICES_CCD_COMPONENT_API_PATH, SERVICES_IDAM_API_PATH, SERVICES_IDAM_ISS_URL, SERVICES_IDAM_WEB, SERVICES_RD_PROFESSIONAL_API_PATH, - SESSION_SECRET, - SERVICES_CCD_COMPONENT_API_PATH + SERVICE_S2S_PATH, + SESSION_SECRET } from '../configuration/references'; import { http } from '../lib/http'; import * as log4jui from '../lib/log4jui'; +import { EnhancedRequest } from '../models/enhanced-request.interface'; import { getOrganisationDetails } from '../organisation'; const logger = log4jui.getLogger('auth'); @@ -92,7 +92,8 @@ export const getXuiNodeMiddleware = () => { const routeCredential = { password, routes: [ - '/external/addresses' + '/external/addresses', + '/external/getLovRefData' ], scope: 'openid profile roles manage-user create-user', userName diff --git a/api/configuration/references.ts b/api/configuration/references.ts index 03d05a2da..a9d7202aa 100644 --- a/api/configuration/references.ts +++ b/api/configuration/references.ts @@ -84,3 +84,6 @@ export const PACT_CONSUMER_VERSION = 'pact.consumerVersion'; export const PACT_BRANCH_NAME = 'pact.branchName'; export const PACT_BROKER_USERNAME = 'pact.brokerUsername'; export const PACT_BROKER_PASSWORD = 'pact.brokerPassword'; + +// LD PREVIEW KEY +export const PREVIEW_LD_KEY = 'previewLDKey'; diff --git a/api/configurationUI/index.ts b/api/configurationUI/index.ts index 7c0656e6c..6e883afa7 100644 --- a/api/configurationUI/index.ts +++ b/api/configurationUI/index.ts @@ -6,6 +6,7 @@ import { LAUNCH_DARKLY_CLIENT_ID, LINKS_MANAGE_CASES_LINK, LINKS_MANAGE_ORG_LINK, + PREVIEW_LD_KEY, PROTOCOL, SERVICE_S2S_PATH, SERVICES_FEE_AND_PAY_API_PATH, @@ -23,18 +24,21 @@ router.get('/', configurationUIRoute); * All the following environmental variables are passed to the UI. */ export async function configurationUIRoute(req, res): Promise { + const environment = process && process.env && process.env.PUI_ENV; + const launchDarklyClientId = environment !== 'preview' ? getConfigValue(LAUNCH_DARKLY_CLIENT_ID) : getConfigValue(PREVIEW_LD_KEY); res.status(200).send({ feeAndPayApiPath: getConfigValue(SERVICES_FEE_AND_PAY_API_PATH), googleAnalyticsKey: getConfigValue(GOOGLE_ANALYTICS_KEY), idamWeb: getConfigValue(SERVICES_IDAM_WEB), - launchDarklyClientId: getConfigValue(LAUNCH_DARKLY_CLIENT_ID), + launchDarklyClientId, manageCaseLink: getConfigValue(LINKS_MANAGE_CASES_LINK), manageOrgLink: getConfigValue(LINKS_MANAGE_ORG_LINK), protocol: getConfigValue(PROTOCOL), rdProfessionalApiPath: getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH), s2sPath: getConfigValue(SERVICE_S2S_PATH), servicesIdamApiPath: getConfigValue(SERVICES_IDAM_API_PATH), - servicesTandCPath: getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH) + servicesTandCPath: getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH), + envrionment: environment ? environment : 'LOCAL' }); } diff --git a/api/models/registrationData.ts b/api/models/registrationData.ts new file mode 100644 index 000000000..3e8181809 --- /dev/null +++ b/api/models/registrationData.ts @@ -0,0 +1,81 @@ +import { AddressModel } from '@hmcts/rpx-xui-common-lib'; + +export interface ContactDetails { + firstName: string; + lastName: string; + workEmailAddress: string; +} + +export interface Regulator { + regulatorType: string; + regulatorName?: string; + organisationRegistrationNumber?: string; +} + +export interface OrganisationType { + key: string; + description: string; +} + +export type RegistrationData = { + companyName: string; + companyHouseNumber?: string; + hasDxReference: boolean; + dxNumber?: string; + dxExchange?: string; + services: string[]; + otherServices?: string; + hasPBA: boolean; + contactDetails: ContactDetails; + address: AddressModel; + organisationType: OrganisationType; + otherOrganisationType?: string; + otherOrganisationDetail?: string; + regulatorRegisteredWith: string; + inInternationalMode: boolean; + regulators: Regulator[]; + hasIndividualRegisteredWithRegulator: boolean; + individualRegulators?: Regulator[]; + pbaNumbers: string[]; +} + +export type RegistrationRequest = { + name: string, + status?: string, + statusMessage?: string, + sraId?: string, + sraRegulated?: string, + companyNumber?: string, + companyUrl?: string, + superUser: { + firstName: string, + lastName: string, + email: string + }, + paymentAccount: string [], + contactInformation: [ + { + uprn?: string, + addressLine1: string, + addressLine2: string, + addressLine3: string, + townCity: string, + county: string, + country: string, + postCode: string, + dxAddress?: [ + { + dxNumber: string, + dxExchange: string + } + ] + } + ], + orgType: string, + orgAttributes?: [ + { + key: string, + value: string + } + ] +} diff --git a/api/openRoutes.ts b/api/openRoutes.ts index a6d498bc7..56c399074 100644 --- a/api/openRoutes.ts +++ b/api/openRoutes.ts @@ -8,6 +8,7 @@ import getLovRefDataRouter from './prd/lov'; // TODO: rename from prdRouter import getappInsightsInstrumentationKey from './monitoring-tools'; import prdRouter from './register-org'; +import registerOrgRouter from './registerOrganisation'; import addressRouter from './addresses'; // TODO: Not sure if this is needed @@ -25,6 +26,7 @@ const router = Router({ mergeParams: true }); * @see local.ts / server.ts */ router.use('/register-org', prdRouter); +router.use('/register-org-new', registerOrgRouter); router.use('/monitoring-tools', getappInsightsInstrumentationKey); router.use('/addresses', addressRouter); diff --git a/api/organisationTypesRouter/mockOrganisationTypes.ts b/api/organisationTypesRouter/mockOrganisationTypes.ts index 823e452e3..1ad3eeadd 100644 --- a/api/organisationTypesRouter/mockOrganisationTypes.ts +++ b/api/organisationTypesRouter/mockOrganisationTypes.ts @@ -1,10 +1,12 @@ export const organisationTypes = [ - { name: 'Solicitor Regulation Authority', id: 'SRA' }, - { name: 'Financial Conduct Authority', id: 'FCA' }, - { name: 'Institute of Charted Accountants for England and Wales', id: 'ICA' }, - { name: 'Charted Institute for Legal Executives', id: 'CILE' }, - { name: 'Certification For Bar Standard Boards', id: 'CFBSS' }, - { name: 'Office of Immigration Services Commissioner (OISC)', id: 'OISC' }, + { name: 'Solicitor Regulation Authority (SRA)', id: 'SRA' }, + { name: 'Financial Conduct Authority (FCA)', id: 'FCA' }, + { name: 'Institute of Charted Accountants in England and Wales', id: 'ICA' }, + { name: 'Charted Institute of Legal Executives', id: 'CILE' }, + { name: 'Certification by the Bar Standards Board', id: 'CFBSS' }, + { name: 'Office of the Immigration Services Commissioner (OISC)', id: 'OISC' }, + { name: 'Law Society of England and Wales', id: 'LSEW' }, + { name: 'Law Society of Scotland', id: 'LSS' }, { name: 'Other', id: 'Other' }, { name: 'Not Applicable', id: 'NA' } ]; diff --git a/api/registerOrganisation/index.ts b/api/registerOrganisation/index.ts new file mode 100644 index 000000000..dbdfe03d3 --- /dev/null +++ b/api/registerOrganisation/index.ts @@ -0,0 +1,80 @@ +import { NextFunction, Request, Response, Router } from 'express'; +import { RegistrationData, RegistrationRequest } from '../models/registrationData'; +import { generateS2sToken } from '../lib/s2sTokenGeneration'; +import { getConfigValue } from '../configuration'; +import { SERVICES_RD_PROFESSIONAL_API_PATH, SERVICE_S2S_PATH } from '../configuration/references'; +import { http } from '../lib/http'; + +export function mapRequestObject(requestBody: RegistrationData): RegistrationRequest { + const request: RegistrationRequest = { + name: requestBody.companyName, + companyNumber: requestBody.companyHouseNumber, + superUser: { + firstName: requestBody.contactDetails.firstName, + lastName: requestBody.contactDetails.lastName, + email: requestBody.contactDetails.workEmailAddress + }, + paymentAccount: requestBody.pbaNumbers, + contactInformation: [ + { + addressLine1: requestBody.address.addressLine1, + addressLine2: convertEmptyStringToNull(requestBody.address.addressLine2), + addressLine3: convertEmptyStringToNull(requestBody.address.addressLine3), + townCity: requestBody.address.postTown, + county: requestBody.address.county, + country: requestBody.address.country, + postCode: requestBody.address.postCode, + dxAddress: getDx(requestBody) + } + ], + orgType: requestBody.organisationType.key + }; + return request; +} + +export const router = Router({ mergeParams: true }); + +function getDx(requestBody: RegistrationData): [{ dxNumber: string; dxExchange: string; }] { + const dxNumber = convertEmptyStringToNull(requestBody.dxNumber); + const dxExchange = convertEmptyStringToNull(requestBody.dxExchange); + if (dxNumber && dxExchange) { + return [{ + dxNumber, + dxExchange + }]; + } + return null; +} + +export async function handleRegisterOrgRoute(req: Request, res: Response, next: NextFunction): Promise { + const registerPayload = req.body as RegistrationData; + + const s2sServicePath = getConfigValue(SERVICE_S2S_PATH); + + const s2sToken = await generateS2sToken(s2sServicePath); + const rdProfessionalPath = getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH); + /** + * We use the S2S token to set the headers. + */ + const url = `${rdProfessionalPath}/refdata/external/v2/organisations`; + const options = { + headers: { ServiceAuthorization: `Bearer ${s2sToken}` } + }; + const axiosInstance = http({} as unknown as Request); + try { + const registerRequest = mapRequestObject(registerPayload); + const response = await axiosInstance.post(url, registerRequest, options); + res.send(response.data); + } catch (error) { + next(error); + } +} + +router.post('/register', handleRegisterOrgRoute); + +export default router; + +function convertEmptyStringToNull(term: string): string { + return term === '' ? null : term; +} + diff --git a/charts/xui-mo-webapp/values.preview.template.yaml b/charts/xui-mo-webapp/values.preview.template.yaml index 89f345c84..4de5f0c56 100644 --- a/charts/xui-mo-webapp/values.preview.template.yaml +++ b/charts/xui-mo-webapp/values.preview.template.yaml @@ -15,10 +15,12 @@ nodejs: #SERVICES_TERMS_AND_CONDITIONS_API_SERVICE: http://xui-terms-and-conditions-aat.service.core-compute-aat.internal #S2S_SERVICE: http://rpe-service-auth-provider-aat.service.core-compute-aat.internal #FEE_AND_PAY_API: https://payment-api-aat.service.core-compute-aat.internal + SERVICES_PRD_COMMONDATA_API: http://rd-commondata-api-aat.service.core-compute-aat.internal MANAGE_CASE_LINK: https://manage-case.aat.platform.hmcts.net/cases MANAGE_ORG_LINK: https://manage-org.aat.platform.hmcts.net/ APPINSIGHTS_INSTRUMENTATIONKEY: ${APPINSIGHTS_INSTRUMENTATIONKEY} + LAUNCH_DARKLY_CLIENT_ID: ${PREVIEW_LD_KEY} PUI_ENV: preview FEATURE_SECURE_COOKIE_ENABLED: false FEATURE_TERMS_AND_CONDITIONS_ENABLED: false diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 67a9bc93b..5921cdaec 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -1,6 +1,7 @@ { "environment": "NODE_CONFIG_ENV", "appInsightsInstrumentationKey": "APPINSIGHTS_INSTRUMENTATIONKEY", + "previewLDKey": "PREVIEW_LD_KEY", "cookies": { "token": "COOKIE_TOKEN", "userId": "COOKIE_USER_ID" diff --git a/config/default.json b/config/default.json index ecc57e638..848816d75 100644 --- a/config/default.json +++ b/config/default.json @@ -1,5 +1,6 @@ { "environment": "DEFAULT ENVIRONMENT", + "previewLDKey": "5de6610b23ce5408280f2268", "stub": false, "appInsightsInstrumentationKey": "", "secrets": { diff --git a/package.json b/package.json index cd86453a5..c10f3a591 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "test:coverage:node": "cd api && yarn coverage", "test:crossbrowser": "protractor ./test/e2e/config/crossbrowser.conf.js", "test:node": "cd api && yarn test", - "test:functional": "yarn test:api && yarn run test:codeceptE2E", + "test:functional": "yarn build && node ./test_codecept/backendMock/configCopy.js && NODE_CONFIG_ENV=mock && yarn playwright install chromium && yarn test:api && yarn run test:xuiIntegration && yarn run test:codeceptE2E", "test:functional:backup": "webdriver-manager update --versions.chrome 2.40 && protractor ./test/e2e/config/functional.conf.js", "test:functional:local": "webdriver-manager update --versions.chrome 2.40 && protractor ./test/e2e/config/functional.conf.js --local", "test:fullfunctional": "yarn test:api && yarn run test:codeceptE2E", @@ -46,13 +46,15 @@ "test:ngIntegrationMockEnv": "node -e 'require(`./test/nodeMock/availablePortFinder.js`).configureTestProxyPort()'", "test:node:watch": "cd api && yarn test:watch", "test:pact:run-and-publish": "yarn test-pact && yarn publish-pact", - "test:smoke": "TEST_TYPE=smoke npx codeceptjs run --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features --grep @smoke ", + "test:smoke": "node ./test_codecept/backendMock/configCopy.js && NODE_CONFIG_ENV=mock TEST_TYPE=smoke npx codeceptjs run --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features", "test:smoke:local": "webdriver-manager update --versions.chrome 2.40 && protractor ./test/e2e/config/smoke.conf.js --local", "test-pact": "NODE_PATH=. NODE_ENV=pacttesting LOG_LEVEL=info mocha --timeout 10000 -r ts-node/register api/test/pact/pact-tests/**/*.spec.ts", "testx": "cd api && yarn coverage && cd .. && yarn test:ng //node tests are broken need to fix", - "test:codeceptE2EDebug": "TEST_TYPE=e2e npx codeceptjs run --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features --grep @functional_test ", - "test:codeceptE2E": "TEST_TYPE=e2e npx codeceptjs run-workers --suites 6 --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features --grep @fullFunctional ", - "test:backendMock": "DEBUG=true nodemon test_codecept/backendMock/app --standalone" + "test:codeceptE2EDebug": "DEBUG=true TEST_TYPE=e2e npx codeceptjs run --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features", + "test:codeceptE2E": "TEST_TYPE=e2e npx codeceptjs run-workers --suites 6 --plugins retryFailedStep --config=./test_codecept/codeceptCommon/codecept.conf.ts --features", + "test:backendMock": "DEBUG=true nodemon test_codecept/backendMock/app --standalone", + "test:xuiIntegrationDebug": "DEBUG=true NODE_CONFIG_ENV=mock TEST_TYPE=ngIntegration npx codeceptjs run -p pauseOnFail --config=./test_codecept/codeceptCommon/codecept.conf.ts --features", + "test:xuiIntegration": "yarn build && node ./test_codecept/backendMock/configCopy.js && NODE_CONFIG_ENV=mock TEST_TYPE=ngIntegration PARALLEL=true npx codeceptjs run-workers --suites 6 --config=./test_codecept/codeceptCommon/codecept.conf.ts --features " }, "private": true, "dependencies": { @@ -74,7 +76,7 @@ "@hmcts/frontend": "0.0.50-alpha", "@hmcts/nodejs-healthcheck": "1.7.0", "@hmcts/properties-volume": "0.0.13", - "@hmcts/rpx-xui-common-lib": "1.9.0-address-display-full", + "@hmcts/rpx-xui-common-lib": "1.9.0-route-expected-feature-2", "@hmcts/rpx-xui-node-lib": "2.27.1", "@ng-idle/core": "^10.0.0", "@ng-idle/keepalive": "^10.0.0", @@ -130,6 +132,7 @@ "node-http-proxy-json": "^0.1.9", "otp": "^0.1.3", "p-iteration": "^1.1.7", + "playwright": "^1.18", "redis": "^3.0.2", "rpx-xui-translation": "^0.1.1", "rx-polling": "^1.1.0", diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f91414bcf..74ae7f625 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -47,7 +47,14 @@ export const ROUTES: Routes = [ }, { path: 'register-org', - loadChildren: () => import('../register/register.module').then((m) => m.RegisterModule) + loadChildren: () => import('../register/register.module').then((m) => m.RegisterModule), + canActivate: [FeatureToggleGuard], + data: { + title: 'Register Organisation', + needsFeaturesEnabled: [AppConstants.FEATURE_NAMES.newRegisterOrg], + expectFeatureEnabled: false, + featureDisabledRedirect: '/register-org-new/register' + } }, { path: 'register-org-new', @@ -56,6 +63,7 @@ export const ROUTES: Routes = [ data: { title: 'Register Organisation', needsFeaturesEnabled: [AppConstants.FEATURE_NAMES.newRegisterOrg], + expectFeatureEnabled: true, featureDisabledRedirect: '/register-org/register' } }, diff --git a/src/custom-validators/postcode.validator.spec.ts b/src/custom-validators/postcode.validator.spec.ts new file mode 100644 index 000000000..d2a936e72 --- /dev/null +++ b/src/custom-validators/postcode.validator.spec.ts @@ -0,0 +1,47 @@ +import { FormControl } from '@angular/forms'; +import { postcodeValidator } from './postcode.validator'; + +describe('postcodeValidator', () => { + let control: FormControl; + + beforeEach(() => { + control = new FormControl({}); + }); + + it('validation should return true, when the first part of postcode is invalid', () => { + control.setValue('l15'); + const validatorFn = postcodeValidator(); + const validationError = validatorFn(control); + expect(validationError).not.toBeNull(); + expect(validationError.invalidPostcode).toBeTruthy(); + }); + + it('validation should return true, when the second part of postcode is invalid', () => { + control.setValue('l15 555'); + const validatorFn = postcodeValidator(); + const validationError = validatorFn(control); + expect(validationError).not.toBeNull(); + expect(validationError.invalidPostcode).toBeTruthy(); + }); + + it('validation should be undefined when valid postcode is entered lowercase', () => { + control.setValue('l155ax'); + const validatorFn = postcodeValidator(); + const validationError = validatorFn(control); + expect(validationError).toBeUndefined(); + }); + + it('validation should be undefined when valid postcode is entered uppercase with space', () => { + control.setValue('L15 5AX'); + const validatorFn = postcodeValidator(); + const validationError = validatorFn(control); + expect(validationError).toBeUndefined(); + }); + + it('validation should be undefined when a valid postcode that is not in postcode database is entered', () => { + control.setValue('ZZ77 7ZZ'); + const validatorFn = postcodeValidator(); + const validationError = validatorFn(control); + expect(validationError).toBeUndefined(); + }); +}); diff --git a/src/custom-validators/postcode.validator.ts b/src/custom-validators/postcode.validator.ts new file mode 100644 index 000000000..1b2adbcc0 --- /dev/null +++ b/src/custom-validators/postcode.validator.ts @@ -0,0 +1,13 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; + +export function postcodeValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.value === null || control.value === '') { + return; + } + if (!control.value.toString().match(/^(([A-Za-z]{1,2}[0-9][A-Za-z0-9]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?[0-9][A-Za-z]{2}|BFPO ?[0-9]{1,4}|(KY[0-9]|MSR|VG|AI)[ -]?[0-9]{4}|[A-Za-z]{2} ?[0-9]{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$/)) { + return { invalidPostcode: true }; + } + return; + }; +} diff --git a/src/models/organisation.model.ts b/src/models/organisation.model.ts index 5edc83965..3daf90acd 100644 --- a/src/models/organisation.model.ts +++ b/src/models/organisation.model.ts @@ -1,3 +1,4 @@ +import { Regulator } from '../register-org/models'; import { PBANumberModel } from './pbaNumber.model'; export interface DxAddress { @@ -33,4 +34,7 @@ export interface OrganisationDetails { pendingAddPaymentAccount: PBANumberModel[]; pendingRemovePaymentAccount: PBANumberModel[]; response?: any; + organisationType?: string; + companyRegistrationNumber?: string; + regulators?: Regulator[]; } diff --git a/src/organisation/containers/organisation/organisation.component.html b/src/organisation/containers/organisation/organisation.component.html index e58b9109d..f0f15860c 100644 --- a/src/organisation/containers/organisation/organisation.component.html +++ b/src/organisation/containers/organisation/organisation.component.html @@ -1,76 +1,201 @@ - - - - - - - - - - + + +
+
+
Name
+
{{organisationDetails.name}}
+
+
+ + +
+
+
Company registration number
+
{{companyRegistrationNumber}}
+
+
+ + +
+
+
Organisation address
+
+
{{organisationContactInformation.addressLine1}}
+
{{organisationContactInformation.addressLine2}}
+
{{organisationContactInformation.townCity}}
+
{{organisationContactInformation.county}}
+
{{organisationContactInformation.postCode}}
+
+
+
+ + +
+
+
DX reference
+
+ {{organisationDxAddress.dxNumber}} + , {{organisationDxAddress.dxExchange}} +
+
+
+ + +
+
+
Organisation type
+
{{organisationType}}
+
+
+ -
- - - - - - - - - - - - - - - - - - - - - - - - - -
Name -
{{organisationDetails.name}}
-
Address - -
{{organisationContactInformation.addressLine1}}
-
{{organisationContactInformation.addressLine2}} -
-
{{organisationContactInformation.townCity}}
-
{{organisationContactInformation.county}}
-
{{organisationContactInformation.postCode}} -
- -
PBA numbers -
-
{{pba.pbaNumber}}
-
-
{{pendingPBA}} (Pending approval)
+
+
+
PBA numbers
+
+
+ {{pba.pbaNumber}} +
+
+
+ + ChangePBA numbers + +
+
+ No PBA accounts registered +
+
+
+ + +
+
+
+ Regulatory organisation type +
+
+
+ + + {{regulator.regulatorType}}: {{regulator.regulatorName}} ref: {{regulator.organisationRegistrationNumber}} + + + {{regulator.regulatorType}} + + + {{regulator.regulatorType}} ref: {{regulator.organisationRegistrationNumber}} + +
+
+
+
+
+
+ + +
+

Administrator details

+
+
+
First name(s)
+
{{organisationDetails.superUser.firstName}}
+
+
+
+
+
Last name
+
{{organisationDetails.superUser.lastName}}
- Change -
PBA number -

Until you've added one or more payment by account (PBA) numbers, - you won't be able to make payments when creating a case or view your transactions.

+ +
+
+
Email address
+
{{organisationDetails.superUser.email}}
-
DX number -
{{organisationDxAddress.dxNumber}}
-
DX exchange -
{{organisationDxAddress.dxExchange}}
-
Administrator -
{{organisationDetails.superUser.firstName}} {{organisationDetails.superUser.lastName}}
-
{{organisationDetails.superUser.email}}
-
SRA ID -
{{organisationDetails.sraId}}
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name +
{{organisationDetails.name}}
+
Address + +
{{organisationContactInformation.addressLine1}}
+
{{organisationContactInformation.addressLine2}} +
+
{{organisationContactInformation.townCity}}
+
{{organisationContactInformation.county}}
+
{{organisationContactInformation.postCode}} +
+ +
PBA numbers +
+
{{pba.pbaNumber}}
+
+
{{pendingPBA}} (Pending approval)
+
+ Change +
PBA number +

Until you've added one or more payment by account (PBA) numbers, + you won't be able to make payments when creating a case or view your transactions.

+
+
DX number +
{{organisationDxAddress.dxNumber}}
+
DX exchange +
{{organisationDxAddress.dxExchange}}
+
Administrator +
{{organisationDetails.superUser.firstName}} {{organisationDetails.superUser.lastName}}
+
{{organisationDetails.superUser.email}}
+
SRA ID +
{{organisationDetails.sraId}}
+
+
diff --git a/src/organisation/containers/organisation/organisation.component.spec.ts b/src/organisation/containers/organisation/organisation.component.spec.ts index 0cff8b636..71d0956b2 100644 --- a/src/organisation/containers/organisation/organisation.component.spec.ts +++ b/src/organisation/containers/organisation/organisation.component.spec.ts @@ -1,6 +1,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; import { combineReducers, Store, StoreModule } from '@ngrx/store'; import { of } from 'rxjs'; @@ -31,6 +32,8 @@ let dispatchSpy: jasmine.Spy; // eslint-disable-next-line @typescript-eslint/no-unused-vars let userIsPuiFinanceManager: boolean; +const featureToggleServiceMock = jasmine.createSpyObj('FeatureToggleService', ['getValue']); + describe('OrganisationComponent', () => { let component: OrganisationComponent; let fixture: ComponentFixture; @@ -76,6 +79,7 @@ describe('OrganisationComponent', () => { beforeEach(() => { pipeSpy = spyOn(storeMock, 'pipe').and.returnValue(of(mockOrganisationDetails)); + featureToggleServiceMock.getValue.and.returnValue(of(true)); dispatchSpy = spyOn(storeMock, 'dispatch'); @@ -97,6 +101,10 @@ describe('OrganisationComponent', () => { provide: Store, useValue: storeMock }, + { + provide: FeatureToggleService, + useValue: featureToggleServiceMock + }, OrganisationComponent ] }).compileComponents(); @@ -153,4 +161,66 @@ describe('OrganisationComponent', () => { expect(component.showChangePbaNumberLink).toBeTruthy(); }); }); + + describe('company registration number visibility', () => { + it('should display company registration number', () => { + component.companyRegistrationNumber = '12345678'; + fixture.detectChanges(); + const companyRegistrationNumberEl = fixture.debugElement.nativeElement.querySelector('#company-registration-number') as HTMLElement; + expect(companyRegistrationNumberEl.textContent).toContain('12345678'); + }); + + it('should not display company registration number', () => { + component.companyRegistrationNumber = ''; + fixture.detectChanges(); + const companyRegistrationNumberEl = fixture.debugElement.nativeElement.querySelector('#company-registration-number') as HTMLElement; + expect(companyRegistrationNumberEl).toBeNull(); + }); + }); + + describe('organisation regulators visibility', () => { + it('should display organisation regulators', () => { + component.regulators = [ + { + regulatorType: 'Solicitor Regulation Authority (SRA)', + organisationRegistrationNumber: '11223344' + }, + { + regulatorType: 'Other', + regulatorName: 'Other regulatory organisation', + organisationRegistrationNumber: '12341234' + }, + { + regulatorType: 'Charted Institute of Legal Executives', + organisationRegistrationNumber: '43214321' + } + ]; + fixture.detectChanges(); + const regulatorsEl = fixture.debugElement.nativeElement.querySelector('#regulators') as HTMLElement; + expect(regulatorsEl.textContent).toContain('Solicitor Regulation Authority (SRA)'); + }); + + it('should not display organisation regulators', () => { + component.regulators = null; + fixture.detectChanges(); + const regulatorsEl = fixture.debugElement.nativeElement.querySelector('#regulators') as HTMLElement; + expect(regulatorsEl).toBeNull(); + }); + }); + + describe('organisation type visibility', () => { + it('should display organisation type', () => { + component.organisationType = 'IT & communications'; + fixture.detectChanges(); + const organisationTypeEl = fixture.debugElement.nativeElement.querySelector('#organisation-type') as HTMLElement; + expect(organisationTypeEl.textContent).toContain('IT & communications'); + }); + + it('should not display organisation type', () => { + component.organisationType = ''; + fixture.detectChanges(); + const organisationTypeEl = fixture.debugElement.nativeElement.querySelector('#organisation-type') as HTMLElement; + expect(organisationTypeEl).toBeNull(); + }); + }); }); diff --git a/src/organisation/containers/organisation/organisation.component.ts b/src/organisation/containers/organisation/organisation.component.ts index 4933f1437..a9619a0bd 100644 --- a/src/organisation/containers/organisation/organisation.component.ts +++ b/src/organisation/containers/organisation/organisation.component.ts @@ -1,8 +1,11 @@ -import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; -import { select, Store } from '@ngrx/store'; -import { Subject } from 'rxjs'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; +import { Store, select } from '@ngrx/store'; +import { Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DxAddress, OrganisationContactInformation, OrganisationDetails, PBANumberModel } from '../../../models'; +import { Regulator, RegulatorType, RegulatoryType } from '../../../register-org/models'; +import { AppConstants } from '../../../app/app.constants'; import * as fromAuthStore from '../../../user-profile/store'; import * as fromStore from '../../store'; import { utils } from '../../utils'; @@ -11,24 +14,34 @@ import { utils } from '../../utils'; selector: 'app-prd-organisation-component', templateUrl: './organisation.component.html' }) -export class OrganisationComponent implements OnDestroy { +export class OrganisationComponent implements OnInit, OnDestroy { public organisationDetails: Partial; public organisationContactInformation: OrganisationContactInformation; public organisationDxAddress: DxAddress; public organisationPaymentAccount: PBANumberModel[]; public organisationPendingPaymentAccount: string[]; public showChangePbaNumberLink: boolean; + public regulatorType = RegulatorType; + public regulatoryTypeEnum = RegulatoryType; + public companyRegistrationNumber: string; + public organisationType: string; + public regulators: Regulator[]; + public isFeatureEnabled$: Observable; private readonly untiDestroy = new Subject(); constructor( private readonly orgStore: Store, private readonly authStore: Store, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly featureToggleService: FeatureToggleService ) { this.getOrganisationDetailsFromStore(); } + ngOnInit(): void { + this.isFeatureEnabled$ = this.featureToggleService.getValue(AppConstants.FEATURE_NAMES.newRegisterOrg, false); + } + public ngOnDestroy(): void { this.untiDestroy.next(); this.untiDestroy.complete(); diff --git a/src/organisation/utils/index.spec.ts b/src/organisation/utils/index.spec.ts index 8d9262efd..94d2de692 100644 --- a/src/organisation/utils/index.spec.ts +++ b/src/organisation/utils/index.spec.ts @@ -90,6 +90,70 @@ describe('organisation.utils', () => { }); }); + describe('getCompanyRegistrationNumber', () => { + it('should return null if there is no company registration number', () => { + const organisationDetails = { + companyRegistrationNumber: '' + }; + expect(utils.getCompanyRegistrationNumber(organisationDetails)).toBeNull(); + }); + + it('should return company registration number', () => { + const organisationDetails = { + companyRegistrationNumber: '12345678' + }; + expect(utils.getCompanyRegistrationNumber(organisationDetails)).toEqual('12345678'); + }); + }); + + describe('getOrganisationType', () => { + it('should return null if there is no organisation type', () => { + const organisationDetails = { + organisationType: '' + }; + expect(utils.getOrganisationType(organisationDetails)).toBeNull(); + }); + + it('should return organisation type', () => { + const organisationDetails = { + organisationType: 'IT & communications' + }; + expect(utils.getOrganisationType(organisationDetails)).toEqual('IT & communications'); + }); + }); + + describe('getRegulators', () => { + it('should return null if there are no regulators', () => { + const organisationDetails = { + regulators: [] + }; + expect(utils.getRegulators(organisationDetails)).toBeNull(); + }); + + it('should return regulators', () => { + const organisationDetails = { + regulators: [ + { + regulatorType: 'Solicitor Regulation Authority (SRA)', + organisationRegistrationNumber: '11223344' + }, + { + regulatorType: 'Other', + regulatorName: 'Other regulatory organisation', + organisationRegistrationNumber: '12341234' + }, + { + regulatorType: 'Charted Institute of Legal Executives', + organisationRegistrationNumber: '43214321' + } + ] + }; + expect(utils.getRegulators(organisationDetails)[0].regulatorType).toContain('Solicitor Regulation Authority (SRA)'); + expect(utils.getRegulators(organisationDetails)[1].regulatorType).toContain('Other'); + expect(utils.getRegulators(organisationDetails)[2].regulatorType).toContain('Charted Institute of Legal Executives'); + }); + }); + describe('getPaymentAccount', () => { it('should return null if there is no paymentAccount', () => { const organisationDetails = {}; diff --git a/src/organisation/utils/index.ts b/src/organisation/utils/index.ts index ad2cffeb6..2265bebe4 100644 --- a/src/organisation/utils/index.ts +++ b/src/organisation/utils/index.ts @@ -1,5 +1,6 @@ import { PBANumberModel } from 'src/models/pbaNumber.model'; import { DxAddress, OrganisationContactInformation, OrganisationDetails } from '../../models'; +import { Regulator } from '../../register-org/models'; import { OrgManagerConstants } from '../organisation-constants'; const containsItems = (obj: object, arrayProperty: string): boolean => { @@ -19,6 +20,24 @@ const utils = { } return null; }, + getCompanyRegistrationNumber: (organisationDetails: Partial): string => { + if (containsItems(organisationDetails, 'companyRegistrationNumber')) { + return organisationDetails.companyRegistrationNumber; + } + return null; + }, + getOrganisationType: (organisationDetails: Partial): string => { + if (containsItems(organisationDetails, 'organisationType')) { + return organisationDetails.organisationType; + } + return null; + }, + getRegulators: (organisationDetails: Partial): Regulator[] => { + if (containsItems(organisationDetails, 'regulators')) { + return organisationDetails.regulators; + } + return null; + }, getPaymentAccount: (organisationDetails: Partial): PBANumberModel[] => { if (containsItems(organisationDetails, 'paymentAccount')) { return organisationDetails.paymentAccount; diff --git a/src/register-org/components/before-you-start/before-you-start.component.spec.ts b/src/register-org/components/before-you-start/before-you-start.component.spec.ts index af136e9c9..1864ccae3 100644 --- a/src/register-org/components/before-you-start/before-you-start.component.spec.ts +++ b/src/register-org/components/before-you-start/before-you-start.component.spec.ts @@ -11,7 +11,8 @@ describe('BeforeYouStartComponent', () => { let fixture: ComponentFixture; const mockRouter = { - navigate: jasmine.createSpy('navigate') + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') }; beforeEach(async () => { diff --git a/src/register-org/components/check-your-answers/check-your-answers.component.html b/src/register-org/components/check-your-answers/check-your-answers.component.html index a173c5f69..dfda1e8b2 100644 --- a/src/register-org/components/check-your-answers/check-your-answers.component.html +++ b/src/register-org/components/check-your-answers/check-your-answers.component.html @@ -1,14 +1,56 @@
- Back + Back +

Check your answers before you register

-

Organisation details

+

Organisation details

-
+
+
+
+
Organisation type
+
Other: {{registrationData.otherOrganisationType.description}}
+
+ + ChangeOrganisation type + +
+
+
+
+
+
Organisation details
+
{{registrationData.otherOrganisationDetail}}
+
+ + ChangeOrganisation details + +
+
+
+
+ +
+
+
Organisation type
+
{{registrationData.organisationType.description}}
+
+ + ChangeOrganisation type + +
+
+
+ +
Organisation name
-
{{ registrationDataToDisplay.name }}
+
{{ registrationData.companyName }}
@@ -18,12 +60,13 @@

Organisation details

-
+
-
+
Organisation address
-
{{ registrationDataToDisplay.address }}
+
+
{{registrationData.address.addressLine1}}
+
{{registrationData.address.addressLine2}}
+
{{registrationData.address.addressLine3}}
+
{{registrationData.address.postTown}}
+
{{registrationData.address.county}}
+
{{registrationData.address.postCode}}
+
{{registrationData.address.country}}
+
- + ChangeOrganisation address
-
+
DX reference
-
{{ registrationDataToDisplay.dxNumber }}
+
+ {{registrationData.dxNumber}}, {{registrationData.dxExchange}} +
- ChangeDX reference + ChangeDX number
- -
+
Service to access
-
{{ registrationDataToDisplay.services }}
+
+
+ {{service}} +
+
@@ -71,87 +127,76 @@

Organisation details

-
+
-
- -
-
-
-
-
What regulator are you (as an individual) registered with?
-
{{ registrationDataToDisplay.regulatorRegisteredWith }}
-
- - ChangeWhat regulator are you (as an individual) registered - with? - -
-
-
-
-

Administration details

+ + -
-
-
First name
-
{{ registrationDataToDisplay.contactDetails.firstName }}
-
- - ChangeFirst name - -
-
-
+

Administration details

+
+
+
+
First name(s)
+
{{registrationData.contactDetails.firstName}}
+
+ + ChangeFirst name(s) + +
+
+
-
-
-
Last name
-
{{ registrationDataToDisplay.contactDetails.lastName }}
-
- +
+
Last name
+
{{registrationData.contactDetails.lastName}}
+
+ + ChangeLast name + +
+
+
+ +
+
+
Email address
+
{{ registrationData.contactDetails.workEmailAddress }}
+
+ - ChangeLast name - -
-
-
+ ChangeEmail address + + +
+
+
-
-
-
Email address
-
{{ registrationDataToDisplay.contactDetails.workEmailAddress }}
-
- - ChangeEmail address - -
-
-
-
-

Confirm your registration details

+ + + +

Confirm your registration details

Ensure that:

  • your personal and organisation detils are correct
  • @@ -168,29 +213,26 @@

    Confirm your registration details

-
-
-
- - +
+ +
+ Error: {{validationErrors[0].message}}
- - + +
- + +
+
+ Cancel
diff --git a/src/register-org/components/check-your-answers/check-your-answers.component.spec.ts b/src/register-org/components/check-your-answers/check-your-answers.component.spec.ts index dd4cab7ac..bdb9983a1 100644 --- a/src/register-org/components/check-your-answers/check-your-answers.component.spec.ts +++ b/src/register-org/components/check-your-answers/check-your-answers.component.spec.ts @@ -1,11 +1,37 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { RegistrationData } from '../../models/registration-data.model'; import { CheckYourAnswersComponent } from './check-your-answers.component'; describe('CheckYourAnswersComponent', () => { let component: CheckYourAnswersComponent; let fixture: ComponentFixture; + let router: Router; + + const registrationData: RegistrationData = { + pbaNumbers: [], + companyName: 'Minstry of Justice', + companyHouseNumber: '11223344', + hasDxReference: null, + dxNumber: null, + dxExchange: null, + services: [], + hasPBA: null, + contactDetails: { + firstName: 'John', + lastName: 'Davis', + workEmailAddress: 'john.davis@testorganisation.com' + }, + address: null, + organisationType: null, + inInternationalMode: true, + regulators: [], + regulatorRegisteredWith: null, + hasIndividualRegisteredWithRegulator: null, + individualRegulators: [] + }; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -20,10 +46,29 @@ describe('CheckYourAnswersComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CheckYourAnswersComponent); component = fixture.componentInstance; + component.registrationData = registrationData; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); fixture.detectChanges(); }); + afterEach(() => { + fixture.destroy(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should back link navigate to the individual registered with the regulator details page', () => { + component.registrationData.hasIndividualRegisteredWithRegulator = true; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'individual-registered-with-regulator-details', true]); + }); + + it('should back link navigate to the individual registered with the regulator page', () => { + component.registrationData.hasIndividualRegisteredWithRegulator = false; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'individual-registered-with-regulator', true]); + }); }); diff --git a/src/register-org/components/check-your-answers/check-your-answers.component.ts b/src/register-org/components/check-your-answers/check-your-answers.component.ts index 8e83e7435..ff51a0048 100644 --- a/src/register-org/components/check-your-answers/check-your-answers.component.ts +++ b/src/register-org/components/check-your-answers/check-your-answers.component.ts @@ -1,19 +1,28 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { HttpErrorResponse } from '@angular/common/http'; import { RegisterComponent } from '../../../register-org/containers'; -import { RegistrationData } from '../../models'; +import { ORGANISATION_SERVICES } from '../../constants/register-org-constants'; +import { RegulatorType, RegulatoryType } from '../../models'; import { RegisterOrgService } from '../../services/register-org.service'; +import { ORGANISATION_TYPES_REF_DATA } from 'src/register-org/__mocks__'; @Component({ selector: 'app-check-your-answers', templateUrl: './check-your-answers.component.html' }) export class CheckYourAnswersComponent extends RegisterComponent implements OnInit { - public registrationDataToDisplay: RegistrationData; + public cyaFormGroup: FormGroup; + public regulatorType = RegulatorType; + public regulatoryType = RegulatoryType; + public services: string[] = []; + public validationErrors: { id: string, message: string }[] = []; + public apiErrors: {id: string, message: string}[] = []; + public readonly errorMessage = 'Please select checkbox to confirm you have read and understood the terms and conditions'; constructor(public readonly router: Router, - public readonly registerOrgService: RegisterOrgService, - private readonly route: ActivatedRoute, + public readonly registerOrgService: RegisterOrgService ) { super(router, registerOrgService); } @@ -21,35 +30,75 @@ export class CheckYourAnswersComponent extends RegisterComponent implements OnIn public ngOnInit(): void { super.ngOnInit(); - // Todo: Test Data, To be deleted - this.registrationDataToDisplay = { - name: 'Test organisation name', - companyHouseNumber: '07911247', - address: '60 Great Prortland Street, London, TE57NG', - hasDxReference: true, - dxNumber: '789MI', - services: ['Employment Tribunals'], - organisationType: 'IT & communications', - organisationNumber: '1234', - regulators: [{ - regulatorType: 'Other', - regulatorName: 'Solicotor Regulation Authority', - organisationRegistrationNumber: '1234' - }], - regulatorRegisteredWith: '123456', - contactDetails: { - firstName: 'John', - lastName: 'Davis', - workEmailAddress: 'John.Davis@testorganisation.com' - }, - hasPBA: true, - hasIndividualRegisteredWithRegulator: true + this.cyaFormGroup = new FormGroup({ + confirmTermsAndConditions: new FormControl(null, [Validators.required, this.getCustomValidationForTermsAndConditions()]) + }); + + this.registrationData.services?.forEach((serviceKey) => { + const service = ORGANISATION_SERVICES.find((service) => service.key === serviceKey).value; + this.services.push(service); + }); + if (this.registrationData.otherServices) { + this.services.push(`Other: ${this.registrationData.otherServices}`); + } + } + + private getCustomValidationForTermsAndConditions(): ValidatorFn { + // TODO: To be used in the functionality ticket if required + return (control: AbstractControl): { [key: string]: any } => { + if (!control.value) { + return { error: this.errorMessage }; + } + return null; }; - const optional = this.route.snapshot.paramMap.get('optional'); - if (optional === 'false') { - delete this.registrationData.companyHouseNumber; - delete this.registrationData.dxNumber; - delete this.registrationData.organisationNumber; + } + + public onBack(): void { + this.registrationData.hasIndividualRegisteredWithRegulator + ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'individual-registered-with-regulator-details', true]) + : this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'individual-registered-with-regulator', true]); + } + + public onSubmitData(): void { + if (this.validateForm()) { + this.registerOrgService.postRegistration().subscribe(() => { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registration-submitted']); + }, + ((errorResponse: HttpErrorResponse) => { + this.apiErrors.push({ + id: 'confirmTermsAndConditions', + message: errorResponse.error.message + }); + })); } } + + private validateForm(): boolean { + this.validationErrors = []; + this.apiErrors = []; + if (!this.cyaFormGroup.valid) { + this.validationErrors.push({ + id: 'confirmTermsAndConditions', + message: this.errorMessage + }); + } + return this.cyaFormGroup.valid; + } + + public termsAndConditionsChange(event: any): void { + this.validateForm(); + } + + public getErrorMessages(): { id: string; message: string;}[] { + return this.apiErrors; + } + + public getOrganisationType(organisationType: string): string { + const orgRefData = ORGANISATION_TYPES_REF_DATA.find((orgType) => orgType.key === organisationType); + return orgRefData ? orgRefData.value_en : null; + } + + public onCancel(): void { + this.cancelRegistrationJourney(); + } } diff --git a/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.html b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.html new file mode 100644 index 000000000..4415379cd --- /dev/null +++ b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.html @@ -0,0 +1,40 @@ +
+
+
+ + What regulators are you (as an individual) registered with? + + + Regulatory organisation type + +
+
+
+ + + {{regulator.regulatorType}}: {{regulator.regulatorName}} ref: {{regulator.organisationRegistrationNumber}} + + + {{regulator.regulatorType}} + + + {{regulator.regulatorType}} ref: {{regulator.organisationRegistrationNumber}} + +
+
+
+
+
+ + ChangeWhat regulators are you (as an individual) registered with? + + + ChangeRegulatory organisation type + +
+
+
diff --git a/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.spec.ts b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.spec.ts new file mode 100644 index 000000000..940cca28d --- /dev/null +++ b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.spec.ts @@ -0,0 +1,95 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RegulatorType } from '../../../../register-org/models'; +import { RegisterOrgService } from '../../../services/register-org.service'; +import { RegulatorListComponent } from './regulator-list.component'; + +describe('RegulatorListComponent', () => { + let component: RegulatorListComponent; + let fixture: ComponentFixture; + let nativeElement: any; + + const mockSessionStorageService = jasmine.createSpyObj('SessionStorageService', [ + 'getItem', + 'setItem', + 'removeItem' + ]); + + const mockHttpService = jasmine.createSpyObj('mockHttpService', ['get', 'post']); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); + + const organisationRegulators = [ + { + regulatorType: 'SRA', + organisationRegistrationNumber: '12334565433' + }, + { + regulatorType: 'CILE', + organisationRegistrationNumber: '123455434333' + }, + { + regulatorType: 'NA' + } + ]; + + const individualRegulators = [ + { + regulatorType: 'Other', + regulatorName: 'qwerty', + organisationRegistrationNumber: '12345' + }, + { + regulatorType: 'OISC', + organisationRegistrationNumber: '76843' + }, + { + regulatorType: 'NA' + } + ]; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RegulatorListComponent], + imports: [], + providers: [{ provide: RegisterOrgService, useValue: service }] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RegulatorListComponent); + component = fixture.componentInstance; + nativeElement = fixture.debugElement.nativeElement; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should verify display of organisation regulators', () => { + component.regulatorType = RegulatorType.Organisation; + component.regulators = organisationRegulators; + fixture.detectChanges(); + const columnHeadingElement = nativeElement.querySelector('.govuk-summary-list__key') as HTMLElement; + expect(columnHeadingElement.innerText).toContain('Regulatory organisation type'); + const columnValueElement = nativeElement.querySelector('.govuk-summary-list__value') as HTMLElement; + expect(columnValueElement.innerText).toContain('SRA ref: 12334565433'); + expect(columnValueElement.innerText).toContain('CILE ref: 123455434333'); + expect(columnValueElement.innerText).toContain('NA'); + }); + + it('should verify display of individual regulators', () => { + component.regulatorType = RegulatorType.Individual; + component.regulators = individualRegulators; + fixture.detectChanges(); + const columnHeadingElement = nativeElement.querySelector('.govuk-summary-list__key') as HTMLElement; + expect(columnHeadingElement.innerText).toContain('What regulators are you (as an individual) registered with?'); + const columnValueElement = nativeElement.querySelector('.govuk-summary-list__value') as HTMLElement; + expect(columnValueElement.innerText).toContain('Other: qwerty ref: 12345'); + expect(columnValueElement.innerText).toContain('OISC ref: 76843'); + expect(columnValueElement.innerText).toContain('NA'); + }); +}); diff --git a/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.ts b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.ts new file mode 100644 index 000000000..66efe4882 --- /dev/null +++ b/src/register-org/components/check-your-answers/regulator-list/regulator-list.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; +import { Regulator, RegulatorType, RegulatoryType } from '../../../models'; +import { RegisterOrgService } from '../../../services/register-org.service'; + +@Component({ + selector: 'app-regulator-list', + templateUrl: './regulator-list.component.html' +}) +export class RegulatorListComponent { + @Input() regulatorType: string; + @Input() regulators: Regulator[]; + + public registerOrgNewRoute: string; + public regulatorTypeEnum = RegulatorType; + public regulatoryTypeEnum = RegulatoryType; + + constructor(private readonly registerOrgService: RegisterOrgService) { + this.registerOrgNewRoute = this.registerOrgService.REGISTER_ORG_NEW_ROUTE; + } +} diff --git a/src/register-org/components/company-house-details/company-house-details.component.html b/src/register-org/components/company-house-details/company-house-details.component.html index ebd14e731..2099ac5b8 100644 --- a/src/register-org/components/company-house-details/company-house-details.component.html +++ b/src/register-org/components/company-house-details/company-house-details.component.html @@ -1,24 +1,8 @@ -
- - - +
- Back + Back + Register an organisation

What is your company name and Companies House number?


diff --git a/src/register-org/components/company-house-details/company-house-details.component.scss b/src/register-org/components/company-house-details/company-house-details.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/register-org/components/company-house-details/company-house-details.component.spec.ts b/src/register-org/components/company-house-details/company-house-details.component.spec.ts index 8f3de195f..3b340961f 100644 --- a/src/register-org/components/company-house-details/company-house-details.component.spec.ts +++ b/src/register-org/components/company-house-details/company-house-details.component.spec.ts @@ -2,26 +2,38 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { CompanyHouseDetailsMessage } from '../../../register-org/models'; +import { RegisterOrgService } from '../../services/register-org.service'; import { CompanyHouseDetailsComponent } from './company-house-details.component'; describe('CompanyHouseDetailsComponent', () => { let component: CompanyHouseDetailsComponent; let fixture: ComponentFixture; - const mockRouter = { - navigate: jasmine.createSpy('navigate') - }; + let router: any; + + const mockSessionStorageService = jasmine.createSpyObj('SessionStorageService', [ + 'getItem', + 'setItem', + 'removeItem' + ]); + + const mockHttpService = jasmine.createSpyObj('mockHttpService', ['get', 'post']); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CompanyHouseDetailsComponent], imports: [RouterTestingModule], - providers: [{ provide: Router, useValue: mockRouter }] + providers: [ + { provide: RegisterOrgService, useValue: service } + ] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CompanyHouseDetailsComponent); component = fixture.componentInstance; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -30,14 +42,14 @@ describe('CompanyHouseDetailsComponent', () => { }); it('should set the error message if company name is null', () => { - const companyNameError = { title: '', description: CompanyHouseDetailsMessage.NO_ORG_NAME, fieldId: 'company-name' }; + const companyNameError = { id: 'company-name', message: CompanyHouseDetailsMessage.NO_ORG_NAME }; component.companyHouseFormGroup.get('companyName').setValue(null); component.onContinue(); expect(component.companyNameError).toEqual(companyNameError); }); it('should set the error message if company number is invalid', () => { - const companyNumberError = { title: '', description: CompanyHouseDetailsMessage.INVALID_COMPANY_NUMBER, fieldId: 'company-house-number' }; + const companyNumberError = { id: 'company-house-number', message: CompanyHouseDetailsMessage.INVALID_COMPANY_NUMBER }; component.companyHouseFormGroup.get('companyHouseNumber').setValue('1234'); component.onContinue(); expect(component.companyNumberError).toEqual(companyNumberError); @@ -47,9 +59,29 @@ describe('CompanyHouseDetailsComponent', () => { component.companyHouseFormGroup.get('companyName').setValue('Company Name'); component.companyHouseFormGroup.get('companyHouseNumber').setValue('12345678'); component.onContinue(); - expect(component.registrationData.name).toEqual('Company Name'); + expect(component.registrationData.companyName).toEqual('Company Name'); expect(component.registrationData.companyHouseNumber).toEqual('12345678'); - expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'registered-address']); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'registered-address', 'external']); + }); + + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the organisation type page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'organisation-type']); }); it('should invoke the cancel registration journey when clicked on cancel link', () => { diff --git a/src/register-org/components/company-house-details/company-house-details.component.ts b/src/register-org/components/company-house-details/company-house-details.component.ts index bbd4b6798..e90443232 100644 --- a/src/register-org/components/company-house-details/company-house-details.component.ts +++ b/src/register-org/components/company-house-details/company-house-details.component.ts @@ -1,8 +1,7 @@ -import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { CompanyHouseDetailsMessage } from '../../../register-org/models'; -import { ErrorMessage } from '../../../shared/models/error-message.model'; import { RegisterComponent } from '../../containers/register/register-org.component'; import { RegisterOrgService } from '../../services/register-org.service'; @@ -11,11 +10,10 @@ import { RegisterOrgService } from '../../services/register-org.service'; templateUrl: './company-house-details.component.html' }) export class CompanyHouseDetailsComponent extends RegisterComponent implements OnInit, OnDestroy { - @ViewChild('mainContent') public mainContentElement: ElementRef; - public validationErrors: ErrorMessage[] = []; + public validationErrors: { id: string, message: string }[] = []; public companyHouseFormGroup: FormGroup; - public companyNameError: ErrorMessage; - public companyNumberError: ErrorMessage; + public companyNameError = null; + public companyNumberError = null; constructor(public readonly router: Router, public readonly registerOrgService: RegisterOrgService, @@ -26,7 +24,7 @@ export class CompanyHouseDetailsComponent extends RegisterComponent implements O public ngOnInit(): void { super.ngOnInit(); this.companyHouseFormGroup = new FormGroup({ - companyName: new FormControl(this.registrationData.name, Validators.required), + companyName: new FormControl(this.registrationData.companyName, Validators.required), companyHouseNumber: new FormControl(this.registrationData.companyHouseNumber, Validators.pattern(/^(((AC|ZC|FC|GE|LP|OC|SE|SA|SZ|SF|GS|SL|SO|SC|ES|NA|NZ|NF|GN|NL|NC|R0|NI|EN|\d{2}|SG|FE)\d{5}(\d|C|R))|((RS|SO)\d{3}(\d{3}|\d{2}[WSRCZF]|\d(FI|RS|SA|IP|US|EN|AS)|CUS))|((NI|SL)\d{5}[\dA])|(OC(([\dP]{5}[CWERTB])|([\dP]{4}(OC|CU)))))$/) ) @@ -39,9 +37,18 @@ export class CompanyHouseDetailsComponent extends RegisterComponent implements O public onContinue(): void { if (this.validateForm()) { - this.registrationData.name = this.companyHouseFormGroup.get('companyName').value; + this.registrationData.companyName = this.companyHouseFormGroup.get('companyName').value; this.registrationData.companyHouseNumber = this.companyHouseFormGroup.get('companyHouseNumber').value; - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-address']); + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-address', 'external']); + } + } + + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'organisation-type']); } } @@ -54,14 +61,13 @@ export class CompanyHouseDetailsComponent extends RegisterComponent implements O if (this.companyHouseFormGroup.invalid) { if (this.companyHouseFormGroup.get('companyName').invalid) { - this.companyNameError = { title: '', description: CompanyHouseDetailsMessage.NO_ORG_NAME, fieldId: 'company-name' }; + this.companyNameError = { id: 'company-name', message: CompanyHouseDetailsMessage.NO_ORG_NAME }; this.validationErrors.push(this.companyNameError); } if (this.companyHouseFormGroup.get('companyHouseNumber').invalid) { - this.companyNumberError = { title: '', description: CompanyHouseDetailsMessage.INVALID_COMPANY_NUMBER, fieldId: 'company-house-number' }; + this.companyNumberError = { id: 'company-house-number', message: CompanyHouseDetailsMessage.INVALID_COMPANY_NUMBER }; this.validationErrors.push(this.companyNumberError); } - this.mainContentElement.nativeElement.scrollIntoView({ behavior: 'smooth' }); return false; } return true; diff --git a/src/register-org/components/contact-details/contact-details.component.html b/src/register-org/components/contact-details/contact-details.component.html index e7cf5f23c..68adf7428 100644 --- a/src/register-org/components/contact-details/contact-details.component.html +++ b/src/register-org/components/contact-details/contact-details.component.html @@ -1,7 +1,8 @@
- Back + Back + Register an organisation

Provide your contact details


@@ -10,27 +11,36 @@

Provide your contact details

See the MyHMCTS guidance on GOV.UK.

-
+
Include all middle names
+

+ Error: {{ firstNameError }} +

-
+
+

+ Error: {{ lastNameError }} +

-
+
+

+ Error: {{ workEmailAddressError }} +

-
diff --git a/src/register-org/components/contact-details/contact-details.component.spec.ts b/src/register-org/components/contact-details/contact-details.component.spec.ts index f28fd00de..76b09029d 100644 --- a/src/register-org/components/contact-details/contact-details.component.spec.ts +++ b/src/register-org/components/contact-details/contact-details.component.spec.ts @@ -1,57 +1,205 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { ContactDetailsErrorMessage } from '../../models/contact-details.enum'; +import { RegistrationData } from '../../models/registration-data.model'; import { ContactDetailsComponent } from './contact-details.component'; describe('ContactDetailsComponent', () => { let component: ContactDetailsComponent; let fixture: ComponentFixture; + const mockRouter = { + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') + }; + const registrationData: RegistrationData = { - name: '', - hasDxReference: true, - dxNumber: '931NR', - dxExchange: 'MIDDLESEX', - hasPBA: null, - contactDetails: { - firstName: 'John', - lastName: 'Davis', - workEmailAddress: 'john.davis@testorganisation.com' - }, - hasIndividualRegisteredWithRegulator: null, + pbaNumbers: [], + companyName: '', + companyHouseNumber: null, + hasDxReference: null, + dxNumber: null, + dxExchange: null, services: [], + hasPBA: null, + contactDetails: null, address: null, organisationType: null, regulators: [], - regulatorRegisteredWith: null + regulatorRegisteredWith: null, + inInternationalMode: null, + hasIndividualRegisteredWithRegulator: null, + individualRegulators: [] }; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ContactDetailsComponent], - imports: [HttpClientTestingModule, RouterTestingModule] - }).compileComponents(); + imports: [ + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: Router, useValue: mockRouter } + ] + }) + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ContactDetailsComponent); component = fixture.componentInstance; + component.registrationData = registrationData; fixture.detectChanges(); }); + afterEach(() => { + fixture.destroy(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); it('should set the form control values', () => { - component.registrationData = registrationData; + component.registrationData.contactDetails = { + firstName: 'John', + lastName: 'Davis', + workEmailAddress: 'john.davis@testorganisation.com' + }; component.setFormControlValues(); expect(component.contactDetailsFormGroup.get('firstName').value).toEqual('John'); expect(component.contactDetailsFormGroup.get('lastName').value).toEqual('Davis'); expect(component.contactDetailsFormGroup.get('workEmailAddress').value).toEqual('john.davis@testorganisation.com'); }); + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the payment by account page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.registrationData.hasPBA = false; + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'payment-by-account']); + }); + + it('should back link navigate to the payment by account details page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.registrationData.hasPBA = true; + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'payment-by-account-details']); + }); + + it('should navigate to individual regulators page if validation is successful', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: 'John', + lastName: 'Davis', + workEmailAddress: 'john.davis@testorganisation.com' + }; + component.setFormControlValues(); + component.onContinue(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'individual-registered-with-regulator']); + }); + + it('should fail validation if all the input fields are empty', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: '', + lastName: '', + workEmailAddress: '' + }; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(3); + expect(component.firstNameError).toEqual(ContactDetailsErrorMessage.FIRST_NAME); + expect(component.lastNameError).toEqual(ContactDetailsErrorMessage.LAST_NAME); + expect(component.workEmailAddressError).toEqual(ContactDetailsErrorMessage.WORK_EMAIL_ADDRESS); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should fail validation if first name input field is empty', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: '', + lastName: 'Davis', + workEmailAddress: 'john.davis@testorganisation.com' + }; + component.setFormControlValues(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(1); + expect(component.firstNameError).toEqual(ContactDetailsErrorMessage.FIRST_NAME); + expect(component.lastNameError).toBeNull(); + expect(component.workEmailAddressError).toBeNull(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should fail validation if last name input field is empty', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: 'John', + lastName: '', + workEmailAddress: 'john.davis@testorganisation.com' + }; + component.setFormControlValues(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(1); + expect(component.firstNameError).toBeNull(); + expect(component.lastNameError).toEqual(ContactDetailsErrorMessage.LAST_NAME); + expect(component.workEmailAddressError).toBeNull(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should fail validation if work email address input field is empty', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: 'John', + lastName: 'Davis', + workEmailAddress: '' + }; + component.setFormControlValues(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(1); + expect(component.firstNameError).toBeNull(); + expect(component.lastNameError).toBeNull(); + expect(component.workEmailAddressError).toEqual(ContactDetailsErrorMessage.WORK_EMAIL_ADDRESS); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should fail validation if work email address is invalid', () => { + mockRouter.navigate.calls.reset(); + component.registrationData.contactDetails = { + firstName: 'John', + lastName: 'Davis', + workEmailAddress: 'john.davis' + }; + component.setFormControlValues(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(1); + expect(component.firstNameError).toBeNull(); + expect(component.lastNameError).toBeNull(); + expect(component.workEmailAddressError).toEqual(ContactDetailsErrorMessage.WORK_EMAIL_ADDRESS); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + it('should invoke the cancel registration journey when clicked on cancel link', () => { spyOn(component, 'cancelRegistrationJourney'); component.onCancel(); diff --git a/src/register-org/components/contact-details/contact-details.component.ts b/src/register-org/components/contact-details/contact-details.component.ts index 8d250cfb0..c1528e9e0 100644 --- a/src/register-org/components/contact-details/contact-details.component.ts +++ b/src/register-org/components/contact-details/contact-details.component.ts @@ -1,8 +1,9 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { RegisterComponent } from '../../containers/register/register-org.component'; +import { ContactDetailsErrorMessage } from '../../models/contact-details.enum'; import { RegisterOrgService } from '../../services/register-org.service'; -import { FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'app-contact-details', @@ -10,6 +11,10 @@ import { FormControl, FormGroup } from '@angular/forms'; }) export class ContactDetailsComponent extends RegisterComponent implements OnInit, OnDestroy { public contactDetailsFormGroup: FormGroup; + public validationErrors: { id: string, message: string }[] = []; + public firstNameError = null; + public lastNameError = null; + public workEmailAddressError = null; constructor(public readonly router: Router, public readonly registerOrgService: RegisterOrgService, @@ -21,9 +26,9 @@ export class ContactDetailsComponent extends RegisterComponent implements OnInit super.ngOnInit(); this.contactDetailsFormGroup = new FormGroup({ - firstName: new FormControl(null), - lastName: new FormControl(null), - workEmailAddress: new FormControl(null) + firstName: new FormControl(null, Validators.required), + lastName: new FormControl(null, Validators.required), + workEmailAddress: new FormControl(null, [Validators.required, Validators.email]) }); this.setFormControlValues(); @@ -40,7 +45,18 @@ export class ContactDetailsComponent extends RegisterComponent implements OnInit lastName: this.contactDetailsFormGroup.get('lastName').value, workEmailAddress: this.contactDetailsFormGroup.get('workEmailAddress').value }; - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-with-regulator']); + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'individual-registered-with-regulator']); + } + } + + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.registrationData.hasPBA + ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'payment-by-account-details']) + : this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'payment-by-account']); } } @@ -57,8 +73,35 @@ export class ContactDetailsComponent extends RegisterComponent implements OnInit } } - public isFormValid(): boolean { - // TODO: Functionality ticket will follow + private isFormValid(): boolean { + this.validationErrors = []; + this.firstNameError = null; + this.lastNameError = null; + this.workEmailAddressError = null; + if (this.contactDetailsFormGroup.invalid) { + if (this.contactDetailsFormGroup.get('firstName').errors) { + this.validationErrors.push({ + id: 'first-name', + message: ContactDetailsErrorMessage.FIRST_NAME + }); + this.firstNameError = ContactDetailsErrorMessage.FIRST_NAME; + } + if (this.contactDetailsFormGroup.get('lastName').errors) { + this.validationErrors.push({ + id: 'last-name', + message: ContactDetailsErrorMessage.LAST_NAME + }); + this.lastNameError = ContactDetailsErrorMessage.LAST_NAME; + } + if (this.contactDetailsFormGroup.get('workEmailAddress').errors) { + this.validationErrors.push({ + id: 'work-email-address', + message: ContactDetailsErrorMessage.WORK_EMAIL_ADDRESS + }); + this.workEmailAddressError = ContactDetailsErrorMessage.WORK_EMAIL_ADDRESS; + } + return false; + } return true; } } diff --git a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.html b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.html index 28cbc94fa..e208c934b 100644 --- a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.html +++ b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.html @@ -1,7 +1,8 @@ -
+
- Back + Back + Register an organisation

What's the DX reference for this office? @@ -9,24 +10,30 @@


-
+
For example: 931NR. You do not need to include the term 'DX'
+

+ Error: {{ dxNumberError.message }} +

-
+
For example: HAYES (MIDDLESEX)
+

+ Error: {{ dxExchangeError.message }} +

diff --git a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.spec.ts b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.spec.ts index 69005c4cd..69f7920fa 100644 --- a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.spec.ts +++ b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.spec.ts @@ -1,15 +1,21 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { DxDetailsMessage } from '../../../register-org/models'; +import { RegistrationData } from '../../models/registration-data.model'; import { DocumentExchangeReferenceDetailsComponent } from './document-exchange-reference-details.component'; describe('DocumentExchangeReferenceComponent', () => { let component: DocumentExchangeReferenceDetailsComponent; let fixture: ComponentFixture; + let router: Router; + const dxNumberError = { message: DxDetailsMessage.INVALID_DX_NUMBER, id: 'dx-number' }; + const dxExchangeError = { message: DxDetailsMessage.INVALID_DX_EXCHANGE, id: 'dx-exchange' }; const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', hasDxReference: true, dxNumber: '931NR', dxExchange: 'MIDDLESEX', @@ -20,7 +26,8 @@ describe('DocumentExchangeReferenceComponent', () => { address: null, organisationType: null, regulators: [], - regulatorRegisteredWith: null + regulatorRegisteredWith: null, + inInternationalMode: null }; beforeEach(async () => { @@ -29,7 +36,8 @@ describe('DocumentExchangeReferenceComponent', () => { imports: [ HttpClientTestingModule, RouterTestingModule - ] + ], + providers: [] }) .compileComponents(); }); @@ -37,6 +45,8 @@ describe('DocumentExchangeReferenceComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(DocumentExchangeReferenceDetailsComponent); component = fixture.componentInstance; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -59,4 +69,86 @@ describe('DocumentExchangeReferenceComponent', () => { component.onCancel(); expect(component.cancelRegistrationJourney).toHaveBeenCalled(); }); + + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the document exchange reference page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'document-exchange-reference']); + }); + + it('should set the validation error if DX number is invalid', () => { + component.registrationData.dxNumber = '12345678912345'; + component.registrationData.dxExchange = '12345678912345678912'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors[0]).toEqual(dxNumberError); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should set the validation error if DX number is invalid', () => { + component.registrationData.dxNumber = '1234567891234'; + component.registrationData.dxExchange = '123456789123456789123'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors[0]).toEqual(dxExchangeError); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should set the validation error if DX number & exchange is invalid', () => { + component.registrationData.dxNumber = '12345678912345'; + component.registrationData.dxExchange = '123456789123456789123'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(2); + expect(component.validationErrors[0]).toEqual(dxNumberError); + expect(component.validationErrors[1]).toEqual(dxExchangeError); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should navigate to the next page if DX number & exchange are valid', () => { + component.registrationData.dxNumber = '1234567891234'; + component.registrationData.dxExchange = '12345678912345678912'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(0); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'regulatory-organisation-type']); + }); + + it('should navigate to the next page if DX number & exchange are valid', () => { + component.registrationData.dxNumber = '1234567891234'; + component.registrationData.dxExchange = '12345678912345678912'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(0); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'regulatory-organisation-type']); + }); + + it('should navigate to the next page if DX number & exchange are empty', () => { + component.registrationData.dxNumber = 'ABC'; + component.registrationData.dxExchange = 'ABC'; + component.setFormControlValues(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(0); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'regulatory-organisation-type']); + }); }); diff --git a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.ts b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.ts index c5dd2ff6c..b3da8a53f 100644 --- a/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.ts +++ b/src/register-org/components/document-exchange-reference-details/document-exchange-reference-details.component.ts @@ -1,15 +1,21 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; +import { DxDetailsMessage } from '../../../register-org/models'; import { RegisterComponent } from '../../containers/register/register-org.component'; import { RegisterOrgService } from '../../services/register-org.service'; -import { FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'app-document-exchange-reference-details', templateUrl: './document-exchange-reference-details.component.html' }) export class DocumentExchangeReferenceDetailsComponent extends RegisterComponent implements OnInit, OnDestroy { + @ViewChild('mainContent') public mainContentElement: ElementRef; + public dxFormGroup: FormGroup; + public validationErrors: { id: string, message: string }[] = []; + public dxNumberError: { id: string, message: string }; + public dxExchangeError: { id: string, message: string }; constructor(public readonly router: Router, public readonly registerOrgService: RegisterOrgService @@ -21,8 +27,8 @@ export class DocumentExchangeReferenceDetailsComponent extends RegisterComponent super.ngOnInit(); this.dxFormGroup = new FormGroup({ - dxNumber: new FormControl(null), - dxExchange: new FormControl(null) + dxNumber: new FormControl(this.registrationData.dxNumber, Validators.maxLength(13)), + dxExchange: new FormControl(this.registrationData.dxExchange, Validators.maxLength(20)) }); this.setFormControlValues(); @@ -33,10 +39,21 @@ export class DocumentExchangeReferenceDetailsComponent extends RegisterComponent } public onContinue(): void { - this.registrationData.dxNumber = this.dxFormGroup.get('dxNumber').value; - this.registrationData.dxExchange = this.dxFormGroup.get('dxExchange').value; - // Navigate to office address page - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'regulatory-organisation-type']); + if (this.validateForm()) { + this.registrationData.dxNumber = this.dxFormGroup.get('dxNumber').value; + this.registrationData.dxExchange = this.dxFormGroup.get('dxExchange').value; + // Navigate to office address page + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'regulatory-organisation-type']); + } + } + + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'document-exchange-reference']); + } } public onCancel(): void { @@ -52,4 +69,21 @@ export class DocumentExchangeReferenceDetailsComponent extends RegisterComponent this.dxFormGroup.get('dxExchange').setValue(this.registrationData.dxExchange); } } + + private validateForm(): boolean { + this.validationErrors = []; + + if (this.dxFormGroup.invalid) { + if (this.dxFormGroup.get('dxNumber').invalid) { + this.dxNumberError = { message: DxDetailsMessage.INVALID_DX_NUMBER, id: 'dx-number' }; + this.validationErrors.push(this.dxNumberError); + } + if (this.dxFormGroup.get('dxExchange').invalid) { + this.dxExchangeError = { message: DxDetailsMessage.INVALID_DX_EXCHANGE, id: 'dx-exchange' }; + this.validationErrors.push(this.dxExchangeError); + } + this.mainContentElement.nativeElement.scrollIntoView({ behavior: 'smooth' }); + } + return this.validationErrors.length === 0; + } } diff --git a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.html b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.html index 60d12a80d..30905ede7 100644 --- a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.html +++ b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.html @@ -17,7 +17,7 @@

- Back + Back Register an organisation
diff --git a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.spec.ts b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.spec.ts index 29470b03b..5b0a989c6 100644 --- a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.spec.ts +++ b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.spec.ts @@ -1,16 +1,19 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { ErrorMessage } from '../../../shared/models/error-message.model'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { RegistrationData } from '../../models/registration-data.model'; import { DocumentExchangeReferenceComponent } from './document-exchange-reference.component'; describe('DocumentExchangeReferenceComponent', () => { let component: DocumentExchangeReferenceComponent; let fixture: ComponentFixture; + let router: Router; const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', hasDxReference: null, dxNumber: null, dxExchange: null, @@ -21,19 +24,26 @@ describe('DocumentExchangeReferenceComponent', () => { address: null, organisationType: null, regulators: [], - regulatorRegisteredWith: null + regulatorRegisteredWith: null, + inInternationalMode: null }; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DocumentExchangeReferenceComponent], - imports: [HttpClientTestingModule, RouterTestingModule] + imports: [ + HttpClientTestingModule, + RouterTestingModule + ], + providers: [] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DocumentExchangeReferenceComponent); component = fixture.componentInstance; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -78,4 +88,24 @@ describe('DocumentExchangeReferenceComponent', () => { component.onCancel(); expect(component.cancelRegistrationJourney).toHaveBeenCalled(); }); + + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the registered address page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'registered-address', 'internal']); + }); }); diff --git a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.ts b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.ts index 4a4eff77e..cb7aaa029 100644 --- a/src/register-org/components/document-exchange-reference/document-exchange-reference.component.ts +++ b/src/register-org/components/document-exchange-reference/document-exchange-reference.component.ts @@ -46,11 +46,20 @@ export class DocumentExchangeReferenceComponent extends RegisterComponent implem // Set corresponding registration data this.registrationData.hasDxReference = false; // Navigate to office address page - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'office-addresses']); + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'regulatory-organisation-type']); } } } + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-address', 'internal']); + } + } + public onCancel(): void { this.cancelRegistrationJourney(); } diff --git a/src/register-org/components/index.ts b/src/register-org/components/index.ts index 6213b766f..f7e64adc3 100644 --- a/src/register-org/components/index.ts +++ b/src/register-org/components/index.ts @@ -1,20 +1,20 @@ import { BeforeYouStartComponent } from './before-you-start/before-you-start.component'; import { CheckYourAnswersComponent } from './check-your-answers/check-your-answers.component'; +import { RegulatorListComponent } from './check-your-answers/regulator-list/regulator-list.component'; import { CompanyHouseDetailsComponent } from './company-house-details/company-house-details.component'; import { ContactDetailsComponent } from './contact-details/contact-details.component'; import { DocumentExchangeReferenceDetailsComponent } from './document-exchange-reference-details/document-exchange-reference-details.component'; import { DocumentExchangeReferenceComponent } from './document-exchange-reference/document-exchange-reference.component'; import { IndividualRegisteredWithRegulatorDetailsComponent } from './individual-registered-with-regulator-details/individual-registered-with-regulator-details.component'; import { IndividualRegisteredWithRegulatorComponent } from './individual-registered-with-regulator/individual-registered-with-regulator.component'; -import { OfficeAddressesComponent } from './office-addresses/office-addresses.component'; import { OrganisationServicesAccessComponent } from './organisation-services-access/organisation-services-access.component'; import { OrganisationTypeComponent } from './organisation-type/organisation-type.component'; import { PaymentByAccountDetailsComponent } from './payment-by-account-details/payment-by-account-details.component'; import { PaymentByAccountComponent } from './payment-by-account/payment-by-account.component'; -import { RegisteredRegulatorComponent } from './registered-regulator/registered-regulator.component'; import { RegistrationSubmittedComponent } from './registration-submitted/registration-submitted.component'; import { RegulatorDetailsComponent } from './regulator-details/regulator-details.component'; import { RegulatoryOrganisationTypeComponent } from './regulatory-organisation-type/regulatory-organisation-type.component'; +import { ServiceDownComponent } from './service-down/service-down.component'; export const components: any[] = [ BeforeYouStartComponent, @@ -25,13 +25,13 @@ export const components: any[] = [ DocumentExchangeReferenceDetailsComponent, IndividualRegisteredWithRegulatorComponent, IndividualRegisteredWithRegulatorDetailsComponent, - OfficeAddressesComponent, OrganisationServicesAccessComponent, OrganisationTypeComponent, PaymentByAccountComponent, PaymentByAccountDetailsComponent, - RegisteredRegulatorComponent, RegistrationSubmittedComponent, RegulatorDetailsComponent, - RegulatoryOrganisationTypeComponent + RegulatorListComponent, + RegulatoryOrganisationTypeComponent, + ServiceDownComponent ]; diff --git a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.html b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.html index 4de3f4114..aabd5ce44 100644 --- a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.html +++ b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.html @@ -1,23 +1,8 @@
- - -
- Back + Back + Register an organisation
@@ -28,20 +13,25 @@


-
-
- - +
+
+ Error: Please select an option
-
- - +
+
+ + +
+
+ + +
diff --git a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.spec.ts b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.spec.ts index ac1311752..a9c562853 100644 --- a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.spec.ts +++ b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.spec.ts @@ -1,15 +1,22 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { ErrorMessage } from '../../../shared/models/error-message.model'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { RegistrationData } from '../../models/registration-data.model'; +import { RegisterOrgService } from '../../services/register-org.service'; import { IndividualRegisteredWithRegulatorComponent } from './individual-registered-with-regulator.component'; describe('IndividualRegisteredWithRegulatorComponent', () => { let component: IndividualRegisteredWithRegulatorComponent; let fixture: ComponentFixture; + const mockRouter = { + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') + }; + const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', hasDxReference: null, dxNumber: null, dxExchange: null, @@ -20,13 +27,26 @@ describe('IndividualRegisteredWithRegulatorComponent', () => { address: null, organisationType: null, regulators: [], - regulatorRegisteredWith: null + regulatorRegisteredWith: null, + inInternationalMode: null }; + const mockSessionStorageService = jasmine.createSpyObj('SessionStorageService', [ + 'getItem', + 'setItem', + 'removeItem' + ]); + + const mockHttpService = jasmine.createSpyObj('mockHttpService', ['get', 'post']); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [IndividualRegisteredWithRegulatorComponent], - imports: [RouterTestingModule] + imports: [RouterTestingModule], + providers: [ + { provide: Router, useValue: mockRouter }, + { provide: RegisterOrgService, useValue: service } + ] }).compileComponents(); }); @@ -36,6 +56,10 @@ describe('IndividualRegisteredWithRegulatorComponent', () => { fixture.detectChanges(); }); + afterEach(() => { + fixture.destroy(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); @@ -54,22 +78,32 @@ describe('IndividualRegisteredWithRegulatorComponent', () => { expect(component.registeredWithRegulatorFormGroup.get('registeredWithRegulator').value).toEqual('no'); }); - it('should set the error message if none of the radio option is selection', () => { - const scrollIntoViewSpy = jasmine.createSpy(); - component.errorSummaryTitleElement = { - nativeElement: { - scrollIntoView: scrollIntoViewSpy - } - }; - const errorMessage: ErrorMessage = { - description: 'Please select at least one option', - title: '', - fieldId: 'registered-with-regulator-yes' + it('should set the error message if none of the radio option is selected', () => { + mockRouter.navigate.calls.reset(); + const errorMessage = { + id: 'registered-with-regulator-yes', + message: 'Please select an option' }; component.registeredWithRegulatorFormGroup.get('registeredWithRegulator').setValue(null); component.onContinue(); - expect(component.registeredWithRegulatorError).toEqual(errorMessage); - expect(scrollIntoViewSpy).toHaveBeenCalled(); + expect(component.validationErrors[0]).toEqual(errorMessage); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should continue navigate to individual regulators details collection page', () => { + component.registrationData = registrationData; + component.registrationData.hasIndividualRegisteredWithRegulator = true; + component.setFormControlValues(); + component.onContinue(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'individual-registered-with-regulator-details']); + }); + + it('should continue navigate to check your answers page', () => { + component.registrationData = registrationData; + component.registrationData.hasIndividualRegisteredWithRegulator = false; + component.setFormControlValues(); + component.onContinue(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); }); it('should invoke the cancel registration journey when clicked on cancel link', () => { @@ -77,4 +111,24 @@ describe('IndividualRegisteredWithRegulatorComponent', () => { component.onCancel(); expect(component.cancelRegistrationJourney).toHaveBeenCalled(); }); + + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the contact details page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'contact-details']); + }); }); diff --git a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.ts b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.ts index d250c5794..64d06e87c 100644 --- a/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.ts +++ b/src/register-org/components/individual-registered-with-regulator/individual-registered-with-regulator.component.ts @@ -1,7 +1,6 @@ -import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { ErrorMessage } from '../../../shared/models/error-message.model'; import { RegisterComponent } from '../../containers/register/register-org.component'; import { RegisterOrgService } from '../../services/register-org.service'; @@ -10,10 +9,8 @@ import { RegisterOrgService } from '../../services/register-org.service'; templateUrl: './individual-registered-with-regulator.component.html' }) export class IndividualRegisteredWithRegulatorComponent extends RegisterComponent implements OnInit, OnDestroy { - @ViewChild('errorSummaryTitleElement') public errorSummaryTitleElement: ElementRef; - public registeredWithRegulatorFormGroup: FormGroup; - public registeredWithRegulatorError: ErrorMessage; + public validationErrors: { id: string, message: string }[] = []; constructor(public readonly router: Router, public readonly registerOrgService: RegisterOrgService, @@ -45,8 +42,7 @@ export class IndividualRegisteredWithRegulatorComponent extends RegisterComponen } else { // Set corresponding registration data this.registrationData.hasIndividualRegisteredWithRegulator = false; - // Note: optional currently a placeholder to make the route work - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'check-your-answers', 'optional']); + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); } } } @@ -55,6 +51,15 @@ export class IndividualRegisteredWithRegulatorComponent extends RegisterComponen this.cancelRegistrationJourney(); } + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'contact-details']); + } + } + public setFormControlValues(): void { if (this.registrationData.hasIndividualRegisteredWithRegulator !== null) { if (this.registrationData.hasIndividualRegisteredWithRegulator) { @@ -67,12 +72,10 @@ export class IndividualRegisteredWithRegulatorComponent extends RegisterComponen private isFormValid(): boolean { if (this.registeredWithRegulatorFormGroup.invalid) { - this.registeredWithRegulatorError = { - description: 'Please select at least one option', - title: '', - fieldId: 'registered-with-regulator-yes' - }; - this.errorSummaryTitleElement.nativeElement.scrollIntoView({ behavior: 'smooth' }); + this.validationErrors.push({ + id: 'registered-with-regulator-yes', + message: 'Please select an option' + }); return false; } return true; diff --git a/src/register-org/components/office-addresses/office-addresses.component.html b/src/register-org/components/office-addresses/office-addresses.component.html deleted file mode 100644 index 6c76475d9..000000000 --- a/src/register-org/components/office-addresses/office-addresses.component.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
-
- Back - Register an organisation -
-
- -

- Office addresses -

-
-
-
-
- -
- Cancel -
-
-
-
diff --git a/src/register-org/components/office-addresses/office-addresses.component.spec.ts b/src/register-org/components/office-addresses/office-addresses.component.spec.ts deleted file mode 100644 index 345fc5800..000000000 --- a/src/register-org/components/office-addresses/office-addresses.component.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { OfficeAddressesComponent } from './office-addresses.component'; - -describe('DocumentExchangeReferenceComponent', () => { - let component: OfficeAddressesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [OfficeAddressesComponent], - imports: [HttpClientTestingModule, RouterTestingModule] - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(OfficeAddressesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should invoke the cancel registration journey when clicked on cancel link', () => { - spyOn(component, 'cancelRegistrationJourney'); - component.onCancel(); - expect(component.cancelRegistrationJourney).toHaveBeenCalled(); - }); -}); diff --git a/src/register-org/components/office-addresses/office-addresses.component.ts b/src/register-org/components/office-addresses/office-addresses.component.ts deleted file mode 100644 index d81b48734..000000000 --- a/src/register-org/components/office-addresses/office-addresses.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { LovRefDataService } from '../../../shared/services/lov-ref-data.service'; -import { RegisterComponent } from '../../containers'; -import { RegisterOrgService } from '../../services'; - -@Component({ - selector: 'app-office-addresses', - templateUrl: './office-addresses.component.html' -}) -export class OfficeAddressesComponent extends RegisterComponent { - constructor(private readonly lovRefDataService: LovRefDataService, - public readonly router: Router, - public readonly registerOrgService: RegisterOrgService) { - super(router, registerOrgService); - } - - public onContinue(): void { - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'organisation-services-access']); - } - - public onCancel(): void { - this.cancelRegistrationJourney(); - } -} diff --git a/src/register-org/components/organisation-services-access/organisation-services-access.component.html b/src/register-org/components/organisation-services-access/organisation-services-access.component.html index a85183506..2b1c8e4c8 100644 --- a/src/register-org/components/organisation-services-access/organisation-services-access.component.html +++ b/src/register-org/components/organisation-services-access/organisation-services-access.component.html @@ -1,7 +1,8 @@
- Back + Back + Register an organisation
@@ -18,13 +19,30 @@

You must select at least one option
-
-
- - +
+
+ Error: {{noServicesError}} +
+
+
+ + +
+
+
+ +
+ Error: {{otherServicesError}} +
+ +
+
diff --git a/src/register-org/components/organisation-services-access/organisation-services-access.component.spec.ts b/src/register-org/components/organisation-services-access/organisation-services-access.component.spec.ts index 7e0ece276..ba795810a 100644 --- a/src/register-org/components/organisation-services-access/organisation-services-access.component.spec.ts +++ b/src/register-org/components/organisation-services-access/organisation-services-access.component.spec.ts @@ -1,12 +1,16 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { OrganisationServicesMessage } from '../../../register-org/models'; import { EnvironmentService } from '../../../shared/services/environment.service'; import { OrganisationServicesAccessComponent } from './organisation-services-access.component'; describe('OrganisationServicesAccessComponent', () => { let component: OrganisationServicesAccessComponent; let fixture: ComponentFixture; + let router: Router; + let nativeElement: any; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -15,7 +19,9 @@ describe('OrganisationServicesAccessComponent', () => { HttpClientTestingModule, RouterTestingModule ], - providers: [EnvironmentService] + providers: [ + EnvironmentService + ] }) .compileComponents(); }); @@ -23,13 +29,59 @@ describe('OrganisationServicesAccessComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(OrganisationServicesAccessComponent); component = fixture.componentInstance; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + nativeElement = fixture.debugElement.nativeElement; fixture.detectChanges(); }); + afterEach(() => { + component.ngOnDestroy(); + TestBed.resetTestingModule(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the company house details page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'company-house-details']); + }); + + it('should not set the error message and navigate to next the page', () => { + nativeElement.querySelector('#AAA7').click(); + fixture.detectChanges(); + component.onContinue(); + expect(component.validationErrors.length).toEqual(0); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'payment-by-account']); + }); + + it('should set the error message and stay on the page', () => { + component.selectedServices = []; + component.services.forEach((s) => s.selected = false); + fixture.detectChanges(); + const error = { message: OrganisationServicesMessage.NO_ORG_SERVICES, id: 'AAA7' }; + component.onContinue(); + expect(component.validationErrors[0]).toEqual(error); + expect(router.navigate).not.toHaveBeenCalled(); + }); + it('should invoke the cancel registration journey when clicked on cancel link', () => { spyOn(component, 'cancelRegistrationJourney'); component.onCancel(); diff --git a/src/register-org/components/organisation-services-access/organisation-services-access.component.ts b/src/register-org/components/organisation-services-access/organisation-services-access.component.ts index af89dd89a..76368aec7 100644 --- a/src/register-org/components/organisation-services-access/organisation-services-access.component.ts +++ b/src/register-org/components/organisation-services-access/organisation-services-access.component.ts @@ -1,10 +1,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; -import { Subscription } from 'rxjs'; -import { LovRefDataModel } from '../../../shared/models/lovRefData.model'; -import { LovRefDataService } from '../../../shared/services/lov-ref-data.service'; -import { SERVICES_REF_DATA } from '../../__mocks__'; +import { OrganisationService, OrganisationServicesMessage } from '../../../register-org/models'; +import { ORGANISATION_SERVICES } from '../../constants/register-org-constants'; import { RegisterComponent } from '../../containers/register/register-org.component'; import { RegisterOrgService } from '../../services/register-org.service'; @@ -14,14 +12,17 @@ import { RegisterOrgService } from '../../services/register-org.service'; }) export class OrganisationServicesAccessComponent extends RegisterComponent implements OnInit, OnDestroy { public readonly CATEGORY_SERVICE_ACCESS = 'Service'; + public readonly SERVICE_NOT_LISTED = 'ServiceNotListed'; public servicesFormGroup: FormGroup; - public lovRefDataSubscription: Subscription; - public services: LovRefDataModel[] = []; + public services: OrganisationService[]; public selectedServices: string[] = []; + public validationErrors: { id: string, message: string }[] = []; + public noServicesError: string; + public otherServicesError: string; + public showOtherServicesInput: boolean; constructor(public readonly router: Router, - public readonly registerOrgService: RegisterOrgService, - private readonly lovRefDataService: LovRefDataService + public readonly registerOrgService: RegisterOrgService ) { super(router, registerOrgService); } @@ -29,16 +30,11 @@ export class OrganisationServicesAccessComponent extends RegisterComponent imple public ngOnInit(): void { super.ngOnInit(); - this.services = SERVICES_REF_DATA; - // TODO: Integration with ref data - // 1. Delete the above line where it uses the mock data - // 2. Uncomment the below lines to integrate with Ref data - // this.lovRefDataService.getListOfValues(this.CATEGORY_SERVICE_ACCESS, false).subscribe((lov) => { - // this.services = lov; - // }); + this.services = ORGANISATION_SERVICES; this.servicesFormGroup = new FormGroup({ - services: new FormControl(null) + services: new FormArray([]), + otherServices: new FormControl(null) }); this.setFormControlValues(); @@ -46,42 +42,77 @@ export class OrganisationServicesAccessComponent extends RegisterComponent imple public ngOnDestroy(): void { super.ngOnDestroy(); - - if (this.lovRefDataSubscription) { - this.lovRefDataSubscription.unsubscribe(); - } } public onServicesSelectionChange(event: any): void { if (event.target.checked) { - this.services.find((service) => service.value_en === event.target.value).selected = true; + this.selectedServices.push(event.target.value); } else { - this.services.find((service) => service.value_en === event.target.value).selected = false; + const serviceIndex = this.selectedServices.findIndex((service) => service === event.target.value); + this.selectedServices.splice(serviceIndex, 1); } + this.showOtherServicesInput = this.selectedServices.includes('NONE'); } public onContinue(): void { if (this.isFormValid()) { - this.selectedServices = this.services.filter((service) => service.selected).map((service) => service.value_en); + this.services = []; // Set corresponding registration data - this.registrationData.services = this.selectedServices; + this.registrationData.services = this.selectedServices.filter((service) => service !== 'NONE'); + + this.registrationData.otherServices = this.showOtherServicesInput + ? this.servicesFormGroup.get('otherServices').value + : null; + // Navigate to collect payment by account details this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'payment-by-account']); } } + public onBack(): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'company-house-details']); + } + } + public onCancel(): void { this.cancelRegistrationJourney(); } - public setFormControlValues(): void { - // TODO: The functionality of setting the checkbox selections - // based on the values from registration data will be handled - // in a separate JIRA ticket + private setFormControlValues(): void { + this.selectedServices = this.registrationData.services; + this.services.forEach((service) => { + service.selected = this.selectedServices.includes(service.key); + }); + if (this.registrationData.otherServices) { + this.showOtherServicesInput = true; + this.selectedServices.push('NONE'); + this.services.find((service) => service.key === 'NONE').selected = true; + this.servicesFormGroup.get('otherServices').setValue(this.registrationData.otherServices); + } } private isFormValid(): boolean { - // TODO: Validation to be handled in a separate JIRA ticket - return true; + this.validationErrors = []; + this.noServicesError = null; + this.otherServicesError = null; + if (!this.selectedServices.length && !this.showOtherServicesInput) { + this.validationErrors.push({ + id: this.services[0].key, + message: OrganisationServicesMessage.NO_ORG_SERVICES + }); + this.noServicesError = OrganisationServicesMessage.NO_ORG_SERVICES; + } + if (this.showOtherServicesInput && !this.servicesFormGroup.get('otherServices').value) { + this.validationErrors.push({ + id: 'other-services', + message: OrganisationServicesMessage.OTHER_SERVICES + }); + this.otherServicesError = OrganisationServicesMessage.OTHER_SERVICES; + } + return this.validationErrors.length === 0; } } diff --git a/src/register-org/components/organisation-type/organisation-type.component.html b/src/register-org/components/organisation-type/organisation-type.component.html index 527d55250..0bba0d47d 100644 --- a/src/register-org/components/organisation-type/organisation-type.component.html +++ b/src/register-org/components/organisation-type/organisation-type.component.html @@ -24,23 +24,14 @@

Select the type of company you are registering. If you are not sure, select 'other'.

-
+
+ (click)="canShowOtherOrganisationTypes(organisationType.key === 'OTHER')">
-
- - -
@@ -56,7 +47,7 @@

[ngClass]="{'govuk-select--error': otherOrgTypeErrors}" id="other-organisation-type" name="otherOrganisationType" formControlName="otherOrganisationType"> - diff --git a/src/register-org/components/organisation-type/organisation-type.component.spec.ts b/src/register-org/components/organisation-type/organisation-type.component.spec.ts index aeebd0675..dc6996a91 100644 --- a/src/register-org/components/organisation-type/organisation-type.component.spec.ts +++ b/src/register-org/components/organisation-type/organisation-type.component.spec.ts @@ -13,13 +13,18 @@ import { OrganisationTypeComponent } from './organisation-type.component'; describe('OrganisationTypeComponent', () => { let component: OrganisationTypeComponent; - let router: Router; let fixture: ComponentFixture; let mockLovRefDataService: any; let nativeElement: any; + const mockRouter = { + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') + }; + const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', hasDxReference: null, dxNumber: null, dxExchange: null, @@ -29,27 +34,28 @@ describe('OrganisationTypeComponent', () => { services: [], address: null, organisationType: null, - regulators: [], - regulatorRegisteredWith: null + inInternationalMode: null, + regulatorRegisteredWith: null, + regulators: [] }; - const OTHER_ORGANISATION_TYPES_REF_DATA: LovRefDataModel[] = [ + const ORGANISATION_TYPES_REF_DATA: LovRefDataModel[] = [ { active_flag: 'Y', - category_key: 'OtherOrgType', + category_key: 'OrgType', child_nodes: null, hint_text_cy: '', hint_text_en: '', - key: 'AccommodationAndFood', + key: 'SolicitorOrganisation', lov_order: null, parent_category: '', parent_key: '', value_cy: '', - value_en: 'Accommodation & Food' + value_en: 'Solicitor Organisation' }, { active_flag: 'Y', - category_key: 'OtherOrgType', + category_key: 'OrgType', child_nodes: null, hint_text_cy: '', hint_text_en: '', @@ -62,29 +68,43 @@ describe('OrganisationTypeComponent', () => { }, { active_flag: 'Y', - category_key: 'OtherOrgType', - child_nodes: null, + category_key: 'OrgType', hint_text_cy: '', hint_text_en: '', - key: 'Education', + key: 'OTHER', lov_order: null, parent_category: '', parent_key: '', value_cy: '', - value_en: 'Education' + value_en: 'Other', + child_nodes: [ + { + active_flag: 'Y', + category_key: 'OtherOrgType', + child_nodes: null, + hint_text_cy: '', + hint_text_en: '', + key: 'Education', + lov_order: null, + parent_category: '', + parent_key: 'OTHER', + value_cy: '', + value_en: 'Education' + } + ] } ]; beforeEach(async () => { mockLovRefDataService = jasmine.createSpyObj('LovRefDataService', ['getListOfValues']); - mockLovRefDataService.getListOfValues.and.returnValue(of(OTHER_ORGANISATION_TYPES_REF_DATA)); + mockLovRefDataService.getListOfValues.and.returnValue(of(ORGANISATION_TYPES_REF_DATA)); await TestBed.configureTestingModule({ declarations: [OrganisationTypeComponent], imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ - { provide: LovRefDataService, useValue: mockLovRefDataService - } + { provide: LovRefDataService, useValue: mockLovRefDataService }, + { provide: Router, useValue: mockRouter } ] }) .compileComponents(); @@ -94,7 +114,6 @@ describe('OrganisationTypeComponent', () => { fixture = TestBed.createComponent(OrganisationTypeComponent); component = fixture.componentInstance; nativeElement = fixture.debugElement.nativeElement; - router = TestBed.inject(Router); component.registrationData = registrationData; fixture.detectChanges(); }); @@ -110,7 +129,6 @@ describe('OrganisationTypeComponent', () => { it('should validate the form on clicking "Continue" and persist data and navigate to next page if validation succeeds', () => { spyOn(component, 'onContinue').and.callThrough(); - spyOn(router, 'navigate'); const continueButton = nativeElement.querySelector('.govuk-button--primary'); const firstButton = nativeElement.querySelectorAll('.govuk-radios__input').item(0); // select an organisation type and continue @@ -119,23 +137,23 @@ describe('OrganisationTypeComponent', () => { continueButton.click(); expect(component.onContinue).toHaveBeenCalled(); expect(component.organisationTypeErrors.length).toBe(0); - expect(router.navigate).toHaveBeenCalled(); - expect(component.registrationData.organisationType).toBe('SolicitorOrganisation'); + expect(mockRouter.navigate).toHaveBeenCalled(); + expect(component.registrationData.organisationType.key).toBe('SolicitorOrganisation'); }); it('should display other organisation types dropdown when other radio option is selected', () => { - nativeElement.querySelector('#other').click(); + nativeElement.querySelector('#OTHER').click(); fixture.detectChanges(); expect(nativeElement.querySelector('#other-organisation-type')).toBeDefined(); expect(nativeElement.querySelector('#other-organisation-detail')).toBeDefined(); }); it('should return validation error for other org with type selected and empty details', () => { + mockRouter.navigate.calls.reset(); component.organisationTypeFormGroup.get('otherOrganisationDetail').setValue(''); component.organisationTypeFormGroup.get('otherOrganisationType').setValue('Test'); spyOn(component, 'onContinue').and.callThrough(); - spyOn(router, 'navigate'); - nativeElement.querySelector('#other').click(); + nativeElement.querySelector('#OTHER').click(); fixture.detectChanges(); const continueButton = nativeElement.querySelector('.govuk-button--primary'); // select an organisation type and continue @@ -146,15 +164,15 @@ describe('OrganisationTypeComponent', () => { id: 'other-organisation-detail', message: OrgTypeMessageEnum.NO_ORG_DETAIS }); - expect(router.navigate).not.toHaveBeenCalled(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); }); it('should return validation error for other org with type not selected and details filled', () => { + mockRouter.navigate.calls.reset(); component.organisationTypeFormGroup.get('otherOrganisationDetail').setValue('Test'); component.organisationTypeFormGroup.get('otherOrganisationType').setValue('none'); spyOn(component, 'onContinue').and.callThrough(); - spyOn(router, 'navigate'); - nativeElement.querySelector('#other').click(); + nativeElement.querySelector('#OTHER').click(); fixture.detectChanges(); const continueButton = nativeElement.querySelector('.govuk-button--primary'); // select an organisation type and continue @@ -165,15 +183,14 @@ describe('OrganisationTypeComponent', () => { id: 'other-organisation-type', message: OrgTypeMessageEnum.NO_ORG_TYPE_SELECTED }); - expect(router.navigate).not.toHaveBeenCalled(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); }); it('should validate the form on clicking "Continue" and persist data if validation succeeds on other', () => { spyOn(component, 'onContinue').and.callThrough(); - spyOn(router, 'navigate'); - nativeElement.querySelector('#other').click(); + nativeElement.querySelector('#OTHER').click(); fixture.detectChanges(); - component.organisationTypeFormGroup.get('otherOrganisationType').setValue('example'); + component.organisationTypeFormGroup.get('otherOrganisationType').setValue('Education'); component.organisationTypeFormGroup.get('otherOrganisationDetail').setValue('text'); const continueButton = nativeElement.querySelector('.govuk-button--primary'); @@ -181,19 +198,30 @@ describe('OrganisationTypeComponent', () => { continueButton.click(); expect(component.onContinue).toHaveBeenCalled(); expect(component.organisationTypeErrors.length).toBe(0); - expect(router.navigate).toHaveBeenCalled(); - expect(component.registrationData.organisationType).toBe('other'); - expect(component.registrationData.otherOrganisationType).toBe('example'); + expect(mockRouter.navigate).toHaveBeenCalled(); + expect(component.registrationData.organisationType.description).toBe('Other'); + expect(component.registrationData.otherOrganisationType.description).toBe('Education'); expect(component.registrationData.otherOrganisationDetail).toBe('text'); }); - it('should clicking on back link navigate back to the first page of the registration', () => { - spyOn(router, 'navigate'); + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the start page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); component.onBack(); - expect(component.registrationData.organisationType).toBeNull(); - expect(component.registrationData.otherOrganisationType).toBeNull(); - expect(component.registrationData.otherOrganisationDetail).toBeNull(); - expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'register']); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new']); }); it('should invoke the cancel registration journey when clicked on cancel link', () => { diff --git a/src/register-org/components/organisation-type/organisation-type.component.ts b/src/register-org/components/organisation-type/organisation-type.component.ts index 63d796ab8..841b756e9 100644 --- a/src/register-org/components/organisation-type/organisation-type.component.ts +++ b/src/register-org/components/organisation-type/organisation-type.component.ts @@ -1,10 +1,9 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { Observable, of } from 'rxjs'; +import { Subscription } from 'rxjs'; import { LovRefDataModel } from '../../../shared/models/lovRefData.model'; import { LovRefDataService } from '../../../shared/services/lov-ref-data.service'; -import { ORGANISATION_TYPES_REF_DATA, OTHER_ORGANISATION_TYPES_REF_DATA } from '../../__mocks__'; import { RegisterComponent } from '../../containers'; import { OrgTypeMessageEnum } from '../../models/organisation-type.enum'; import { RegisterOrgService } from '../../services'; @@ -17,14 +16,14 @@ export class OrganisationTypeComponent extends RegisterComponent implements OnIn @ViewChild('mainContent') public mainContentElement: ElementRef; public readonly CATEGORY_ORGANISATION_TYPE = 'OrgType'; - public readonly CATEGORY_OTHER_ORGANISATION_TYPE = 'OrgSubType'; public organisationTypeFormGroup: FormGroup; public organisationTypeErrors: { id: string, message: string }[] = []; public otherOrgTypeErrors: { id: string, message: string }; public otherOrgDetailsErrors: { id: string, message: string }; - public organisationTypes$: Observable; - public otherOrganisationTypes$: Observable; + public organisationTypes: LovRefDataModel[]; + public otherOrganisationTypes: LovRefDataModel[]; + public subscription: Subscription; public showOtherOrganisationTypes = false; constructor(private readonly lovRefDataService: LovRefDataService, @@ -36,31 +35,44 @@ export class OrganisationTypeComponent extends RegisterComponent implements OnIn public ngOnInit(): void { super.ngOnInit(); this.organisationTypeFormGroup = new FormGroup({ - organisationType: new FormControl(this.registrationData.organisationType, Validators.required), - otherOrganisationType: new FormControl(this.registrationData.otherOrganisationType), + organisationType: new FormControl(this.registrationData.organisationType?.key, Validators.required), + otherOrganisationType: new FormControl(this.registrationData.otherOrganisationType?.key), otherOrganisationDetail: new FormControl(this.registrationData.otherOrganisationDetail) }); - if (this.registrationData.organisationType !== 'other') { + if (this.registrationData.organisationType?.key !== 'OTHER') { this.organisationTypeFormGroup.get('otherOrganisationType').setValue('none'); this.organisationTypeFormGroup.get('otherOrganisationDetail').setValue(''); } else { this.showOtherOrganisationTypes = true; } - this.organisationTypes$ = of(ORGANISATION_TYPES_REF_DATA); - this.otherOrganisationTypes$ = of(OTHER_ORGANISATION_TYPES_REF_DATA); - // TODO: Integration with ref data - // 1. Delete the above two lines where it uses the mock data - // 2. Uncomment the below two lines to integrate with Ref data - // this.organisationTypes$ = this.lovRefDataService.getListOfValues(this.CATEGORY_ORGANISATION_TYPE, false); - // this.otherOrganisationTypes$ = this.lovRefDataService.getListOfValues(this.CATEGORY_OTHER_ORGANISATION_TYPE, false); + this.subscription = this.lovRefDataService.getListOfValues(this.CATEGORY_ORGANISATION_TYPE, true).subscribe((orgTypes) => { + this.organisationTypes = orgTypes; + + const otherTypes = orgTypes.find((orgType) => orgType.key === 'OTHER').child_nodes; + this.otherOrganisationTypes = otherTypes; + }, (error) => this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'service-down'])); } public onContinue(): void { if (this.isFormValid()) { - this.registrationData.organisationType = this.organisationTypeFormGroup.get('organisationType').value; - this.registrationData.otherOrganisationType = this.showOtherOrganisationTypes ? this.organisationTypeFormGroup.get('otherOrganisationType').value : null; - this.registrationData.otherOrganisationDetail = this.showOtherOrganisationTypes ? this.organisationTypeFormGroup.get('otherOrganisationDetail').value : null; + // Choose known orgType from radio + const orgTypeSelected = { + key: this.organisationTypeFormGroup.get('organisationType').value, + description: this.organisationTypes.find((orgType) => orgType.key === this.organisationTypeFormGroup.get('organisationType').value).value_en + }; + this.registrationData.organisationType = orgTypeSelected; + this.registrationData.otherOrganisationType = null; + this.registrationData.otherOrganisationDetail = null; + if (this.showOtherOrganisationTypes) { + // Other type selected from dropdown + const otherOrgTypeSelected = { + key: this.organisationTypeFormGroup.get('otherOrganisationType').value, + description: this.otherOrganisationTypes.find((orgType) => orgType.key === this.organisationTypeFormGroup.get('otherOrganisationType').value).value_en + }; + this.registrationData.otherOrganisationType = otherOrgTypeSelected; + this.registrationData.otherOrganisationDetail = this.organisationTypeFormGroup.get('otherOrganisationDetail').value; + } this.registerOrgService.persistRegistrationData(this.registrationData); this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'company-house-details']); } @@ -71,11 +83,12 @@ export class OrganisationTypeComponent extends RegisterComponent implements OnIn } public onBack(): void { - this.registrationData.organisationType = null; - this.registrationData.otherOrganisationType = null; - this.registrationData.otherOrganisationDetail = null; - this.registerOrgService.persistRegistrationData(this.registrationData); - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'register']); + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE]); + } } private isFormValid(): boolean { @@ -114,6 +127,7 @@ export class OrganisationTypeComponent extends RegisterComponent implements OnIn } public ngOnDestroy(): void { + this.subscription.unsubscribe(); super.ngOnDestroy(); } } diff --git a/src/register-org/components/payment-by-account-details/payment-by-account-details.component.html b/src/register-org/components/payment-by-account-details/payment-by-account-details.component.html index dd9a3bfa0..60335667f 100644 --- a/src/register-org/components/payment-by-account-details/payment-by-account-details.component.html +++ b/src/register-org/components/payment-by-account-details/payment-by-account-details.component.html @@ -1,15 +1,18 @@
- Back + Back Register an organisation

What PBA numbers does your organisation use?


-
+
+

+ Error:{{summaryErrors.items[i].message}} +

- +
Cancel diff --git a/src/register-org/components/regulator-details/regulator-details.component.spec.ts b/src/register-org/components/regulator-details/regulator-details.component.spec.ts index 68f611973..5102cbdcc 100644 --- a/src/register-org/components/regulator-details/regulator-details.component.spec.ts +++ b/src/register-org/components/regulator-details/regulator-details.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { @@ -18,27 +18,34 @@ describe('RegulatorDetailsComponent', () => { let component: RegulatorDetailsComponent; let fixture: ComponentFixture; let mockLovRefDataService: any; + let router: Router; let nativeElement: any; - const mockRouter = { - navigate: jasmine.createSpy('navigate') + + const mockRoute = { + snapshot: { + params: { + backLinkTriggeredFromCYA: true + } + } }; const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', + companyHouseNumber: null, hasDxReference: null, dxNumber: null, dxExchange: null, services: [], hasPBA: null, contactDetails: null, - companyHouseNumber: null, address: null, organisationType: null, - organisationNumber: null, regulators: [], regulatorRegisteredWith: null, hasIndividualRegisteredWithRegulator: null, - individualRegulators: [] + individualRegulators: [], + inInternationalMode: null }; const organisationTypes = [ @@ -57,7 +64,7 @@ describe('RegulatorDetailsComponent', () => { schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { - provide: Router, useValue: mockRouter + provide: ActivatedRoute, useValue: mockRoute }, { provide: LovRefDataService, useValue: mockLovRefDataService @@ -73,6 +80,8 @@ describe('RegulatorDetailsComponent', () => { nativeElement = fixture.debugElement.nativeElement; component.registrationData = registrationData; spyOn(component, 'onOptionSelected').and.callThrough(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -101,8 +110,8 @@ describe('RegulatorDetailsComponent', () => { selectElement.selectedIndex = 1; selectElement.dispatchEvent(new Event('change')); fixture.detectChanges(); - expect(component.regulators.controls[0].get('regulatorType').value).toEqual('SRA'); - expect(component.onOptionSelected).toHaveBeenCalledWith('SRA', 0); + expect(component.regulators.controls[0].get('regulatorType').value).toEqual('Solicitor Regulation Authority'); + expect(component.onOptionSelected).toHaveBeenCalledWith('Solicitor Regulation Authority', 0); expect(component.regulators.controls[0].get('regulatorName')).toBeFalsy(); expect(component.regulators.controls[0].get('organisationRegistrationNumber')).toBeTruthy(); expect(nativeElement.querySelector('#regulator-name0')).toBeFalsy(); @@ -137,7 +146,7 @@ describe('RegulatorDetailsComponent', () => { selectElement.selectedIndex = 4; selectElement.dispatchEvent(new Event('change')); fixture.detectChanges(); - expect(component.regulators.controls[0].get('regulatorType').value).toEqual('NA'); + expect(component.regulators.controls[0].get('regulatorType').value).toEqual('Not Applicable'); }); it('should add a new regulator entry when the "Add another regulator" button is clicked', () => { @@ -239,15 +248,14 @@ describe('RegulatorDetailsComponent', () => { selectElement1.selectedIndex = 4; selectElement1.dispatchEvent(new Event('change')); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.registrationData.regulators.length).toEqual(1); expect(component.registrationData.regulators[0].regulatorType).toEqual(RegulatoryType.NotApplicable); - expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'organisation-services-access']); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'organisation-services-access']); }); it('should validate the form on clicking "Continue" and not persist data or navigate to next page if validation fails', () => { - mockRouter.navigate.calls.reset(); - spyOn(component, 'onContinueClicked').and.callThrough(); + spyOn(component, 'onContinue').and.callThrough(); component.validationErrors = []; component.registrationData.regulators = []; component.setFormControlValues(); @@ -264,7 +272,7 @@ describe('RegulatorDetailsComponent', () => { const continueButton = nativeElement.querySelector('.govuk-button--primary'); continueButton.click(); fixture.detectChanges(); - expect(component.onContinueClicked).toHaveBeenCalled(); + expect(component.onContinue).toHaveBeenCalled(); expect(component.validationErrors.length).toBe(3); expect(component.validationErrors[0]).toEqual({ id: 'regulator-name0', @@ -274,11 +282,11 @@ describe('RegulatorDetailsComponent', () => { id: 'organisation-registration-number0', message: RegulatoryOrganisationTypeMessage.NO_REGISTRATION_NUMBER }); - expect(mockRouter.navigate).not.toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); }); it('should validate the form on clicking "Continue" and persist data and navigate to next page if validation succeeds', () => { - spyOn(component, 'onContinueClicked').and.callThrough(); + spyOn(component, 'onContinue').and.callThrough(); component.registrationData.regulators = []; component.setFormControlValues(); fixture.detectChanges(); @@ -309,9 +317,9 @@ describe('RegulatorDetailsComponent', () => { const continueButton = nativeElement.querySelector('.govuk-button--primary'); continueButton.click(); fixture.detectChanges(); - expect(component.onContinueClicked).toHaveBeenCalled(); + expect(component.onContinue).toHaveBeenCalled(); expect(component.validationErrors.length).toBe(0); - expect(mockRouter.navigate).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); it('should set the error message if regulator name is empty for a known regulator type', () => { @@ -323,7 +331,7 @@ describe('RegulatorDetailsComponent', () => { component.setFormControlValues(); fixture.detectChanges(); const registrationNumberError = { message: RegulatoryOrganisationTypeMessage.NO_REGISTRATION_NUMBER, id: 'organisation-registration-number0' }; - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors[0]).toEqual(registrationNumberError); }); @@ -338,7 +346,7 @@ describe('RegulatorDetailsComponent', () => { fixture.detectChanges(); const reglatorNameError = { message: RegulatoryOrganisationTypeMessage.NO_REGULATOR_NAME, id: 'regulator-name0' }; const registrationNumberError = { message: RegulatoryOrganisationTypeMessage.NO_REGISTRATION_NUMBER, id: 'organisation-registration-number0' }; - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors[0]).toEqual(reglatorNameError); expect(component.validationErrors[1]).toEqual(registrationNumberError); }); @@ -346,11 +354,11 @@ describe('RegulatorDetailsComponent', () => { it('should not set the error message if regulator type is NA', () => { component.regulatorType = RegulatorType.Organisation; component.registrationData.regulators = [{ - regulatorType: 'NA' + regulatorType: 'Not Applicable' }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -362,7 +370,7 @@ describe('RegulatorDetailsComponent', () => { }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -375,7 +383,7 @@ describe('RegulatorDetailsComponent', () => { }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -392,7 +400,7 @@ describe('RegulatorDetailsComponent', () => { }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -410,7 +418,7 @@ describe('RegulatorDetailsComponent', () => { component.setFormControlValues(); fixture.detectChanges(); const duplicateError = { message: RegulatoryOrganisationTypeMessage.DUPLICATE_REGULATOR_BANNER, id: 'regulator-type0' }; - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors[0]).toEqual(duplicateError); }); @@ -430,7 +438,7 @@ describe('RegulatorDetailsComponent', () => { component.setFormControlValues(); fixture.detectChanges(); const duplicateError = { message: RegulatoryOrganisationTypeMessage.DUPLICATE_REGULATOR_BANNER, id: 'regulator-type0' }; - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors[0]).toEqual(duplicateError); }); @@ -449,7 +457,7 @@ describe('RegulatorDetailsComponent', () => { }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -468,7 +476,7 @@ describe('RegulatorDetailsComponent', () => { }]; component.setFormControlValues(); fixture.detectChanges(); - component.onContinueClicked(); + component.onContinue(); expect(component.validationErrors.length).toEqual(0); }); @@ -477,4 +485,33 @@ describe('RegulatorDetailsComponent', () => { component.onCancel(); expect(component.cancelRegistrationJourney).toHaveBeenCalled(); }); + + it('should back link navigate to the individual registered with regulator page', () => { + mockRoute.snapshot.params.backLinkTriggeredFromCYA = true; + component.regulatorType = RegulatorType.Individual; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'individual-registered-with-regulator']); + }); + + it('should back link navigate to the document exchange reference details page', () => { + mockRoute.snapshot.params.backLinkTriggeredFromCYA = true; + component.regulatorType = RegulatorType.Organisation; + component.registrationData.hasDxReference = true; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'document-exchange-reference-details']); + }); + + it('should back link navigate to the document exchange reference page', () => { + mockRoute.snapshot.params.backLinkTriggeredFromCYA = true; + component.regulatorType = RegulatorType.Organisation; + component.registrationData.hasDxReference = false; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'document-exchange-reference']); + }); + + it('should back link navigate to the check your answers page', () => { + mockRoute.snapshot.params.backLinkTriggeredFromCYA = false; + component.onBack(); + expect(router.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); }); diff --git a/src/register-org/components/regulator-details/regulator-details.component.ts b/src/register-org/components/regulator-details/regulator-details.component.ts index c8c3b73bf..696a2b4e9 100644 --- a/src/register-org/components/regulator-details/regulator-details.component.ts +++ b/src/register-org/components/regulator-details/regulator-details.component.ts @@ -1,6 +1,6 @@ import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Observable } from 'rxjs'; import { LovRefDataService } from '../../../shared/services/lov-ref-data.service'; import { RegisterComponent } from '../../containers/register/register-org.component'; @@ -32,6 +32,7 @@ export class RegulatorDetailsComponent extends RegisterComponent implements OnIn constructor( private readonly lovRefDataService: LovRefDataService, + private route: ActivatedRoute, public readonly router: Router, public readonly registerOrgService: RegisterOrgService ) { @@ -137,12 +138,12 @@ export class RegulatorDetailsComponent extends RegisterComponent implements OnIn ); } - public onContinueClicked(): void { + public onContinue(): void { if (this.validateForm()) { // Set corresponding registration data this.setRegulatorData(); this.regulatorType === RegulatorType.Individual - ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'check-your-answers', 'true']) + ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]) : this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'organisation-services-access']); } } @@ -152,9 +153,19 @@ export class RegulatorDetailsComponent extends RegisterComponent implements OnIn } public onBack(): void { - this.regulatorType === RegulatorType.Individual - ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'individual-registered-with-regulator']) - : this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'organisation-services-access']); + if (this.route.snapshot?.params?.backLinkTriggeredFromCYA) { + // Back link triggered from CYA page + if (this.regulatorType === RegulatorType.Individual) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'individual-registered-with-regulator']); + } else { + this.registrationData.hasDxReference + ? this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'document-exchange-reference-details']) + : this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'document-exchange-reference']); + } + } else { + // Change link triggered from CYA page + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } } public fieldHasErrorMessage(fieldId: string): boolean { diff --git a/src/register-org/components/service-down/service-down.component.html b/src/register-org/components/service-down/service-down.component.html new file mode 100644 index 000000000..acabf821e --- /dev/null +++ b/src/register-org/components/service-down/service-down.component.html @@ -0,0 +1,8 @@ +
+
+
+

Sorry, there is a problem with the service

+

Try again later.

+
+
+
\ No newline at end of file diff --git a/src/register-org/components/service-down/service-down.component.spec.ts b/src/register-org/components/service-down/service-down.component.spec.ts new file mode 100644 index 000000000..497acef18 --- /dev/null +++ b/src/register-org/components/service-down/service-down.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ServiceDownComponent } from './service-down.component'; + +describe('ServiceDownComponent', () => { + let component: ServiceDownComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ServiceDownComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ServiceDownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/register-org/components/service-down/service-down.component.ts b/src/register-org/components/service-down/service-down.component.ts new file mode 100644 index 000000000..f282c9155 --- /dev/null +++ b/src/register-org/components/service-down/service-down.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-service-down', + templateUrl: './service-down.component.html' +}) +export class ServiceDownComponent { + +} diff --git a/src/register-org/constants/register-org-constants.ts b/src/register-org/constants/register-org-constants.ts index d96c102ff..f906a0fa4 100644 --- a/src/register-org/constants/register-org-constants.ts +++ b/src/register-org/constants/register-org-constants.ts @@ -1,2 +1,45 @@ export const POSTCODE_HEADING = 'What is the registered address of your organisation?'; export const INTERNATIONAL_HEADING = 'Is this a UK address?'; + +export const ORGANISATION_SERVICES = [ + { + key: 'AAA7', + value: 'Damages' + }, + { + key: 'ABA1', + value: 'Divorce' + }, + { + key: 'BHA1', + value: 'Employment Claims' + }, + { + key: 'ABA2', + value: 'Financial Remedy' + }, + { + key: 'ABA3', + value: 'Family Public Law' + }, + { + key: 'ABA5', + value: 'Family Private Law' + }, + { + key: 'BFA1', + value: 'Immigration and Asylum Appeals' + }, + { + key: 'ABA6', + value: 'Probate' + }, + { + key: 'AAA6', + value: 'Specified Money Claims' + }, + { + key: 'NONE', + value: 'Service not listed' + } +]; diff --git a/src/register-org/containers/register/register-org.component.spec.ts b/src/register-org/containers/register/register-org.component.spec.ts index acd5ad02d..5234618dd 100644 --- a/src/register-org/containers/register/register-org.component.spec.ts +++ b/src/register-org/containers/register/register-org.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { RegistrationData } from '../../models/registration-data.model'; import { RegisterOrgService } from '../../services'; import { RegisterComponent } from './register-org.component'; @@ -18,11 +18,13 @@ describe('RegisterComponent', () => { mockRegisterOrgService.REGISTER_ORG_NEW_ROUTE = 'register-org-new'; const mockRouter = { - navigate: jasmine.createSpy('navigate') + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') }; const registrationData = { - name: '', + companyName: '', + companyHouseNumber: null, hasDxReference: null, dxNumber: null, dxExchange: null, @@ -30,10 +32,8 @@ describe('RegisterComponent', () => { hasPBA: null, contactDetails: null, hasIndividualRegisteredWithRegulator: null, - companyHouseNumber: null, address: null, organisationType: null, - organisationNumber: null, regulators: [], regulatorRegisteredWith: null } as RegistrationData; diff --git a/src/register-org/containers/register/register-org.component.ts b/src/register-org/containers/register/register-org.component.ts index 18452fb00..43cd0f52e 100644 --- a/src/register-org/containers/register/register-org.component.ts +++ b/src/register-org/containers/register/register-org.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { RegistrationData } from '../../models/registrationdata.model'; +import { Navigation, Router } from '@angular/router'; +import { RegistrationData } from '../../models/registration-data.model'; import { RegisterOrgService } from '../../services/index'; @Component({ @@ -9,13 +9,19 @@ import { RegisterOrgService } from '../../services/index'; }) export class RegisterComponent implements OnInit, OnDestroy { - constructor(public readonly router: Router, - public readonly registerOrgService: RegisterOrgService) {} - private isRegistrationJourneyCancelled = false; - + private routerCurrentNavigation: Navigation; public registrationData: RegistrationData; + constructor(public readonly router: Router, + public readonly registerOrgService: RegisterOrgService) { + this.routerCurrentNavigation = this.router.getCurrentNavigation(); + } + + public get currentNavigation(): Navigation { + return this.routerCurrentNavigation; + } + public ngOnInit(): void { this.initialiseRegistrationJourney(); } diff --git a/src/register-org/containers/registered-address/registered-address.component.html b/src/register-org/containers/registered-address/registered-address.component.html index f3b832c43..32b77eb9c 100644 --- a/src/register-org/containers/registered-address/registered-address.component.html +++ b/src/register-org/containers/registered-address/registered-address.component.html @@ -2,9 +2,10 @@
- Back - Back + Back + Back + Register an organisation

{{headingText}}


@@ -14,17 +15,21 @@

{{headingText}}

company information register.

-
- +
-
Cancel -
diff --git a/src/register-org/containers/registered-address/registered-address.component.spec.ts b/src/register-org/containers/registered-address/registered-address.component.spec.ts index a6464bd74..3a6fe5256 100644 --- a/src/register-org/containers/registered-address/registered-address.component.spec.ts +++ b/src/register-org/containers/registered-address/registered-address.component.spec.ts @@ -1,21 +1,48 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { AddressMessageEnum } from '@hmcts/rpx-xui-common-lib'; import { INTERNATIONAL_HEADING, POSTCODE_HEADING } from '../../constants/register-org-constants'; +import { RegisterOrgService } from '../../services'; import { RegisteredAddressComponent } from './registered-address.component'; describe('RegisteredAddressComponent', () => { let component: RegisteredAddressComponent; let fixture: ComponentFixture; + let mockRegisterOrgService: any; + + const mockRoute = { + snapshot: { + params: { + internal: 'internal' + } + } + }; + + const mockRouter = { + navigate: jasmine.createSpy('navigate'), + getCurrentNavigation: jasmine.createSpy('getCurrentNavigation') + }; beforeEach(async () => { + mockRegisterOrgService = jasmine.createSpyObj('mockRegisterOrgService', ['getRegistrationData', 'persistRegistrationData', 'REGISTER_ORG_NEW_ROUTE', 'CHECK_YOUR_ANSWERS_ROUTE']); + mockRegisterOrgService.getRegistrationData.and.returnValue({ address: null }); + mockRegisterOrgService.REGISTER_ORG_NEW_ROUTE = 'register-org-new'; + mockRegisterOrgService.CHECK_YOUR_ANSWERS_ROUTE = 'check-your-answers'; await TestBed.configureTestingModule({ declarations: [RegisteredAddressComponent], imports: [ HttpClientTestingModule, RouterTestingModule ], - providers: [] + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [{ provide: RegisterOrgService, useValue: mockRegisterOrgService }, + { + provide: Router, useValue: mockRouter + }, + { provide: ActivatedRoute, useValue: mockRoute }] }) .compileComponents(); }); @@ -37,8 +64,109 @@ describe('RegisteredAddressComponent', () => { component.onInternationalModeStart(); fixture.detectChanges(); + expect(component.startedInternational).toBe(true); + expect(component.addressChosen).toBe(false); + expect(component.headingText).toBe(INTERNATIONAL_HEADING); + }); + + it('should get a persisted address', () => { + const mockRegData = { + address: { + addressLine1: 'street', + addressLine2: 'street2', + addressLine3: 'extraStreet', + postTown: 'city', + country: 'unknown', + postCode: 'L12 7RT' + }, + inInternationalMode: false + }; + mockRegisterOrgService.getRegistrationData.and.returnValue(mockRegData); + component.ngOnInit(); + expect(component.formGroup.get('address').get('addressLine1').value).toBe('street'); + mockRegData.inInternationalMode = true; + mockRegisterOrgService.getRegistrationData.and.returnValue(mockRegData); + component.ngOnInit(); + expect(component.formGroup.get('address').get('addressLine1').value).toBe('street'); + expect(component.startedInternational).toBeTruthy(); + expect(component.isInternational).toBeTruthy(); + expect(component.registrationData.inInternationalMode).toBeTruthy(); + }); + + it('should get a persisted postcode', () => { + const mockRegData = { + address: { + addressLine1: 'street', + addressLine2: 'street2', + addressLine3: 'extraStreet', + postTown: 'city', + country: 'unknown', + postCode: 'L12 7RT' + }, + inInternationalMode: false + }; + mockRegisterOrgService.getRegistrationData.and.returnValue(mockRegData); + component.isInternal = false; + component.ngOnInit(); + expect(component.formGroup.get('address').get('postCode').value).toBe('L12 7RT'); + }); + + it('should check the form on continue', () => { component.startedInternational = true; - component.headingText = INTERNATIONAL_HEADING; + component.isInternational = undefined; + + component.onContinue(); + + component.addressErrors = [{ + id: 'govuk-radios', + message: AddressMessageEnum.NO_OPTION_SELECTED + }]; + component.submissionAttempted = true; + + const mockRegData = { + address: { + addressLine1: 'street', + addressLine2: 'street2', + addressLine3: 'extraStreet', + postTown: 'city', + country: 'unknown', + postCode: 'L12 7RT' + }, + inInternationalMode: false + }; + mockRegisterOrgService.getRegistrationData.and.returnValue(mockRegData); + component.startedInternational = true; + component.isInternational = true; + component.submissionAttempted = false; + component.ngOnInit(); + component.onContinue(); + + expect(component.registrationData.address).toBe(mockRegData.address); + expect(component.registrationData.inInternationalMode).toBeTruthy(); + expect(component.submissionAttempted).toBeFalsy(); + expect(mockRouter.navigate).toHaveBeenCalled(); + }); + + it('should set international or non-international when option selected', () => { + const mockRegData = { + address: { + addressLine1: 'street', + addressLine2: 'street2', + addressLine3: 'extraStreet', + postTown: 'city', + country: 'unknown', + postCode: 'L12 7RT' + }, + inInternationalMode: false + }; + mockRegisterOrgService.getRegistrationData.and.returnValue(mockRegData); + component.isInternal = true; + component.ngOnInit(); + component.onOptionSelected(true); + + expect(component.isInternational).toBeTruthy(); + expect(component.submissionAttempted).toBeFalsy(); + expect(component.formGroup.get('address').get('country').value).toBe(''); }); it('should invoke the cancel registration journey when clicked on cancel link', () => { @@ -46,4 +174,60 @@ describe('RegisteredAddressComponent', () => { component.onCancel(); expect(component.cancelRegistrationJourney).toHaveBeenCalled(); }); + + it('should set postcode option details correctly', () => { + component.onPostcodeOptionSelected(); + expect(component.submissionAttempted).toBe(false); + expect(component.addressChosen).toBe(true); + expect(component.addressErrors).toEqual([]); + }); + + it('should reset submission', () => { + component.submissionAttempted = true; + component.onResetSubmission(); + expect(component.submissionAttempted).toBe(false); + }); + + it('should back link navigate to the check your answers page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/check-your-answers' + } + }); + component.onBack(true); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'check-your-answers']); + }); + + it('should back link navigate to the registered address page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(true); + expect(component.headingText).toEqual('What is the registered address of your organisation?'); + expect(component.startedInternational).toEqual(false); + expect(component.addressChosen).toBe(false); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'registered-address', 'external']); + }); + + it('should back link navigate to the company house details page', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: '/something-else' + } + }); + component.onBack(false); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'company-house-details']); + }); + + it('should back link navigate to the company house details page without a previousUrl', () => { + spyOnProperty(component, 'currentNavigation', 'get').and.returnValue({ + previousNavigation: { + finalUrl: undefined + } + }); + component.onBack(false); + expect(mockRouter.navigate).toHaveBeenCalledWith(['register-org-new', 'company-house-details']); + }); }); diff --git a/src/register-org/containers/registered-address/registered-address.component.ts b/src/register-org/containers/registered-address/registered-address.component.ts index 765c6e618..1554892c3 100644 --- a/src/register-org/containers/registered-address/registered-address.component.ts +++ b/src/register-org/containers/registered-address/registered-address.component.ts @@ -1,6 +1,8 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { Router } from '@angular/router'; +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AddressMessageEnum, AddressModel } from '@hmcts/rpx-xui-common-lib'; +import { postcodeValidator } from '../../../custom-validators/postcode.validator'; import { INTERNATIONAL_HEADING, POSTCODE_HEADING } from '../../constants/register-org-constants'; import { RegisterComponent } from '../../containers/register/register-org.component'; import { RegisterOrgService } from '../../services/register-org.service'; @@ -10,18 +12,54 @@ import { RegisterOrgService } from '../../services/register-org.service'; templateUrl: './registered-address.component.html' }) export class RegisteredAddressComponent extends RegisterComponent implements OnInit, OnDestroy { + @ViewChild('mainContent') public mainContentElement: ElementRef; + public formGroup = new FormGroup({}); public startedInternational = false; + public addressChosen = false; public headingText = POSTCODE_HEADING; + public isInternational: boolean; + + public addressErrors: { id: string, message: string }[] = []; + + public submissionAttempted = false; + + public isInternal = false; + + public postcodeErrorFound = false; + + private addressSelectable = false; + constructor(public readonly router: Router, public readonly registerOrgService: RegisterOrgService, + public readonly route: ActivatedRoute ) { super(router, registerOrgService); + this.isInternal = this.route.snapshot.params.internal === 'internal' ? true : false; } public ngOnInit(): void { super.ngOnInit(); + if (this.isInternal) { + this.setExistingFormGroup(); + } else if (this.addressExists()) { + this.setFormGroup(this.registrationData.address); + } + } + + private setExistingFormGroup(): void { + if (this.addressExists()) { + if (this.registrationData.inInternationalMode) { + this.startedInternational = true; + this.headingText = INTERNATIONAL_HEADING; + this.isInternational = this.registrationData.address.country !== 'UK'; + } else { + // check to ensure the user has not just started international mode + this.addressChosen = !this.startedInternational ? true : false; + } + this.setFormGroup(this.registrationData.address); + } } public ngOnDestroy(): void { @@ -29,19 +67,152 @@ export class RegisteredAddressComponent extends RegisterComponent implements OnI } public onContinue(): void { - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'document-exchange-reference']); + if (this.isFormValid()) { + this.registrationData.address = this.formGroup.get('address').value; + this.registrationData.inInternationalMode = this.startedInternational; + + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'document-exchange-reference']); + } else { + this.submissionAttempted = true; + } } public onCancel(): void { this.cancelRegistrationJourney(); } - public onBack(): void { - this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-address']); + public onBack(refreshPage: boolean): void { + const previousUrl = this.currentNavigation?.previousNavigation?.finalUrl?.toString(); + if (previousUrl?.includes(this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE)) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, this.registerOrgService.CHECK_YOUR_ANSWERS_ROUTE]); + } else { + if (refreshPage) { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'registered-address', 'external']); + this.headingText = POSTCODE_HEADING; + this.startedInternational = false; + this.addressChosen = false; + } else { + this.router.navigate([this.registerOrgService.REGISTER_ORG_NEW_ROUTE, 'company-house-details']); + } + } + } + + private isFormValid(): boolean { + let errorFound = false; + this.addressErrors = []; + if (!this.addressChosen && !this.startedInternational) { + this.addressErrors.push({ + id: this.addressSelectable ? 'selectAddress' : 'addressLookup', + message: this.addressSelectable ? AddressMessageEnum.SELECT_ADDRESS : AddressMessageEnum.NO_POSTCODE_SELECTED + }); + errorFound = true; + } else if (this.startedInternational && this.isInternational === undefined) { + this.addressErrors.push({ + id: 'govuk-radios', + message: AddressMessageEnum.NO_OPTION_SELECTED + }); + errorFound = true; + } else if (this.formGroup.invalid) { + if (this.isControlInvalid('addressLine1')) { + this.addressErrors.push({ + id: 'addressLine1', + message: AddressMessageEnum.NO_STREET_SELECTED + }); + } + if (this.isControlInvalid('postTown')) { + this.addressErrors.push({ + id: 'postTown', + message: AddressMessageEnum.NO_CITY_SELECTED + }); + } + if (this.isControlInvalid('country') && this.isInternational) { + this.addressErrors.push({ + id: 'country', + message: AddressMessageEnum.NO_COUNTRY_SELECTED + }); + } + if (this.isControlInvalid('postCode') && !this.isInternational) { + this.addressErrors.push({ + id: 'postCode', + message: AddressMessageEnum.NO_POSTCODE_SELECTED + }); + } + // Invalid Input + if (!errorFound) { + if (this.formGroup.get('address').get('postCode').hasError('invalidPostcode')) { + this.addressErrors.push({ + id: 'postCode', + message: AddressMessageEnum.INVALID_POSTCODE + }); + } + } + errorFound = true; + } + return errorFound ? false : true; + } + + private isControlInvalid(control: string): boolean { + return !this.formGroup.get('address').get(control).value || this.formGroup.get('address').get(control).value === ''; + } + + public onPostcodeOptionSelected(): void { + this.submissionAttempted = false; + this.addressChosen = true; + this.addressErrors = []; + this.setAddressFormGroup(); } public onInternationalModeStart(): void { this.startedInternational = true; + this.addressChosen = false; this.headingText = INTERNATIONAL_HEADING; + this.addressErrors = []; + this.addressExists() ? this.setExistingFormGroup() : this.setAddressFormGroup(); + } + + public onResetSubmission(): void { + this.submissionAttempted = false; + } + + public onOptionSelected(isInternational: boolean): void { + this.isInternational = isInternational; + this.submissionAttempted = false; + this.formGroup.get('address').get('country').setValidators(this.isInternational ? [Validators.required] : null); + this.formGroup.get('address').get('postCode').setValidators(this.isInternational ? null : [Validators.required, postcodeValidator()]); + this.formGroup.get('address').get('country').updateValueAndValidity(); + this.formGroup.get('address').get('postCode').updateValueAndValidity(); + this.formGroup.setErrors(null); + this.formGroup.get('address').get('country').patchValue(isInternational ? '' : 'UK'); + } + + public onAddressSelectable(addressSelectable: boolean): void { + this.addressSelectable = addressSelectable; + } + + private setAddressFormGroup(): void { + if (!this.formGroup.get('address')) { + this.setFormGroup(); + return; + } + const givenAddress = this.formGroup.get('address').value; + givenAddress.postCode && givenAddress.postCode !== '' ? this.setFormGroup(givenAddress) : this.setFormGroup(); + } + + private setFormGroup(givenAddress?: AddressModel) { + this.formGroup = new FormGroup({ + address: new FormGroup({ + addressLine1: new FormControl(givenAddress ? givenAddress.addressLine1 : null, Validators.required), + addressLine2: new FormControl(givenAddress ? givenAddress.addressLine2 : null, null), + addressLine3: new FormControl(givenAddress ? givenAddress.addressLine3 : null, null), + postTown: new FormControl(givenAddress ? givenAddress.postTown : null, Validators.required), + county: new FormControl(givenAddress ? givenAddress.county : null, null), + country: new FormControl(givenAddress ? givenAddress.country : (this.isInternational ? null: 'UK'), this.isInternational ? Validators.required : null), + postCode: new FormControl(givenAddress ? givenAddress.postCode : null, this.isInternational ? null : [Validators.required, postcodeValidator()]) + }) + }); + } + + private addressExists(): boolean { + return this.registrationData.address && this.registrationData.address.addressLine1 && this.registrationData.address.addressLine1 !== ''; } } diff --git a/src/register-org/models/address.model.ts b/src/register-org/models/address.model.ts new file mode 100644 index 000000000..4a936ee3b --- /dev/null +++ b/src/register-org/models/address.model.ts @@ -0,0 +1,11 @@ +// TODO: Delete this file once the Address model in CommonLib is available +// and reference it directly from CommonLib +export interface Address { + addressLine1: string; + addressLine2?: string; + addressLine3?: string; + postTown: string; + county?: string; + postCode: string; + country: string; +} diff --git a/src/register-org/models/contact-details.enum.ts b/src/register-org/models/contact-details.enum.ts new file mode 100644 index 000000000..95b85713b --- /dev/null +++ b/src/register-org/models/contact-details.enum.ts @@ -0,0 +1,5 @@ +export enum ContactDetailsErrorMessage { + FIRST_NAME = 'Enter first name', + LAST_NAME = 'Enter last name', + WORK_EMAIL_ADDRESS = 'Enter email address' +} diff --git a/src/register-org/models/error-messages.model.ts b/src/register-org/models/error-messages.model.ts index a5e2773f0..9eaac9273 100644 --- a/src/register-org/models/error-messages.model.ts +++ b/src/register-org/models/error-messages.model.ts @@ -2,3 +2,13 @@ export enum CompanyHouseDetailsMessage { NO_ORG_NAME = 'Enter an organisation name', INVALID_COMPANY_NUMBER = 'Enter a valid Companies House number' } + +export enum DxDetailsMessage { + INVALID_DX_NUMBER = 'Enter valid DX number', + INVALID_DX_EXCHANGE = 'Enter valid DX exchange' +} + +export enum OrganisationServicesMessage { + NO_ORG_SERVICES = 'Please select at least one service', + OTHER_SERVICES = 'Enter one or more services' +} diff --git a/src/register-org/models/index.ts b/src/register-org/models/index.ts index b96372e7a..71f93d41f 100644 --- a/src/register-org/models/index.ts +++ b/src/register-org/models/index.ts @@ -1,6 +1,9 @@ +export * from './address.model'; +export * from './contact-details.enum'; export * from './contact-details.model'; export * from './error-messages.model'; -export * from './registrationdata.model'; +export * from './organisation-service.model'; +export * from './registration-data.model'; export * from './regulator-type.enum'; export * from './regulator.model'; export * from './regulatory-organisation-type.model'; diff --git a/src/register-org/models/organisation-service.model.ts b/src/register-org/models/organisation-service.model.ts new file mode 100644 index 000000000..5f8b6238d --- /dev/null +++ b/src/register-org/models/organisation-service.model.ts @@ -0,0 +1,5 @@ +export interface OrganisationService { + key: string; + value: string; + selected?: boolean; +} diff --git a/src/register-org/models/registrationdata.model.ts b/src/register-org/models/registration-data.model.ts similarity index 54% rename from src/register-org/models/registrationdata.model.ts rename to src/register-org/models/registration-data.model.ts index 532eccb24..7180bc575 100644 --- a/src/register-org/models/registrationdata.model.ts +++ b/src/register-org/models/registration-data.model.ts @@ -1,22 +1,32 @@ +import { AddressModel } from '@hmcts/rpx-xui-common-lib'; import { ContactDetails } from './contact-details.model'; import { Regulator } from './regulator.model'; +// TODO: Reference Address model from CommonLib once it is available + export type RegistrationData = { - name: string; + companyName: string; + companyHouseNumber?: string; hasDxReference: boolean; dxNumber?: string; dxExchange?: string; services: string[]; + otherServices?: string; hasPBA: boolean; contactDetails: ContactDetails; - companyHouseNumber?: string; - address: string; - organisationType: string; - otherOrganisationType?: string; + address: AddressModel; + organisationType: OrganisationType; + otherOrganisationType?: OrganisationType; otherOrganisationDetail?: string; - organisationNumber?: string; regulatorRegisteredWith: string; + inInternationalMode: boolean; regulators: Regulator[]; hasIndividualRegisteredWithRegulator: boolean; individualRegulators?: Regulator[]; + pbaNumbers: string[]; +} + +export interface OrganisationType { + key: string; + description: string; } diff --git a/src/register-org/models/regulatory-organisation.enum.ts b/src/register-org/models/regulatory-organisation.enum.ts index 2af2d10fd..511012adf 100644 --- a/src/register-org/models/regulatory-organisation.enum.ts +++ b/src/register-org/models/regulatory-organisation.enum.ts @@ -1,6 +1,6 @@ export enum RegulatoryType { Other = 'Other', - NotApplicable = 'NA' + NotApplicable = 'Not Applicable' } export enum RegulatoryOrganisationTypeMessage { diff --git a/src/register-org/register-org.routing.ts b/src/register-org/register-org.routing.ts index c9ed8ae30..d64b5807c 100644 --- a/src/register-org/register-org.routing.ts +++ b/src/register-org/register-org.routing.ts @@ -8,14 +8,13 @@ import { DocumentExchangeReferenceDetailsComponent } from './components/document import { DocumentExchangeReferenceComponent } from './components/document-exchange-reference/document-exchange-reference.component'; import { IndividualRegisteredWithRegulatorDetailsComponent } from './components/individual-registered-with-regulator-details/individual-registered-with-regulator-details.component'; import { IndividualRegisteredWithRegulatorComponent } from './components/individual-registered-with-regulator/individual-registered-with-regulator.component'; -import { OfficeAddressesComponent } from './components/office-addresses/office-addresses.component'; import { OrganisationServicesAccessComponent } from './components/organisation-services-access/organisation-services-access.component'; import { OrganisationTypeComponent } from './components/organisation-type/organisation-type.component'; import { PaymentByAccountDetailsComponent } from './components/payment-by-account-details/payment-by-account-details.component'; import { PaymentByAccountComponent } from './components/payment-by-account/payment-by-account.component'; -import { RegisteredRegulatorComponent } from './components/registered-regulator/registered-regulator.component'; import { RegistrationSubmittedComponent } from './components/registration-submitted/registration-submitted.component'; import { RegulatoryOrganisationTypeComponent } from './components/regulatory-organisation-type/regulatory-organisation-type.component'; +import { ServiceDownComponent } from './components/service-down/service-down.component'; import { RegisteredAddressComponent } from './containers'; import { RegisterOrgModule } from './register-org.module'; @@ -49,10 +48,6 @@ export const ROUTES: Routes = [ path: 'document-exchange-reference-details', component: DocumentExchangeReferenceDetailsComponent }, - { - path: 'office-addresses', - component: OfficeAddressesComponent - }, { path: 'organisation-services-access', component: OrganisationServicesAccessComponent @@ -66,7 +61,7 @@ export const ROUTES: Routes = [ component: PaymentByAccountDetailsComponent }, { - path: 'registered-address', + path: 'registered-address/:internal', component: RegisteredAddressComponent }, { @@ -78,8 +73,8 @@ export const ROUTES: Routes = [ component: IndividualRegisteredWithRegulatorDetailsComponent }, { - path: 'registered-regulator', - component: RegisteredRegulatorComponent + path: 'individual-registered-with-regulator-details/:backLinkTriggeredFromCYA', + component: IndividualRegisteredWithRegulatorDetailsComponent }, { path: 'contact-details', @@ -90,8 +85,12 @@ export const ROUTES: Routes = [ component: RegistrationSubmittedComponent }, { - path: 'check-your-answers/:optional', + path: 'check-your-answers', component: CheckYourAnswersComponent + }, + { + path: 'service-down', + component: ServiceDownComponent } ]; diff --git a/src/register-org/services/register-org.service.spec.ts b/src/register-org/services/register-org.service.spec.ts index d0a02c538..c5264d20c 100644 --- a/src/register-org/services/register-org.service.spec.ts +++ b/src/register-org/services/register-org.service.spec.ts @@ -1,8 +1,11 @@ -import { RegistrationData } from '../models/registrationdata.model'; +import { of } from 'rxjs'; +import { RegistrationData } from '../models/registration-data.model'; import { RegisterOrgService } from './register-org.service'; const registrationData: RegistrationData = { - name: '', + pbaNumbers: [], + companyName: '', + companyHouseNumber: null, hasDxReference: null, dxNumber: null, dxExchange: null, @@ -10,12 +13,11 @@ const registrationData: RegistrationData = { hasPBA: null, contactDetails: null, hasIndividualRegisteredWithRegulator: null, - companyHouseNumber: null, address: null, organisationType: null, - organisationNumber: null, regulators: [], - regulatorRegisteredWith: null + regulatorRegisteredWith: null, + inInternationalMode: null }; describe('RegisterOrgService', () => { @@ -25,9 +27,12 @@ describe('RegisterOrgService', () => { 'removeItem' ]); + const mockHttpService = jasmine.createSpyObj('mockHttpService', ['get', 'post']); + mockHttpService.post.and.returnValue(of({})); + it('should get registration data', () => { mockSessionStorageService.getItem.and.returnValue(JSON.stringify(registrationData)); - const service = new RegisterOrgService(mockSessionStorageService); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); const registrationDataResult = service.getRegistrationData(); expect(registrationDataResult).toEqual(registrationData); expect(mockSessionStorageService.getItem).toHaveBeenCalled(); @@ -35,14 +40,14 @@ describe('RegisterOrgService', () => { it('should persist registration data', () => { mockSessionStorageService.setItem.and.callThrough(); - const service = new RegisterOrgService(mockSessionStorageService); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); service.persistRegistrationData(registrationData); expect(mockSessionStorageService.setItem).toHaveBeenCalled(); }); it('should remove registration data', () => { mockSessionStorageService.removeItem.and.callThrough(); - const service = new RegisterOrgService(mockSessionStorageService); + const service = new RegisterOrgService(mockSessionStorageService, mockHttpService); service.removeRegistrationData(); expect(mockSessionStorageService.removeItem).toHaveBeenCalled(); }); diff --git a/src/register-org/services/register-org.service.ts b/src/register-org/services/register-org.service.ts index 3c799b305..f7b40d48f 100644 --- a/src/register-org/services/register-org.service.ts +++ b/src/register-org/services/register-org.service.ts @@ -1,6 +1,8 @@ +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; import { SessionStorageService } from '../../shared/services/session-storage.service'; -import { RegistrationData } from '../models/registrationdata.model'; +import { RegistrationData } from '../models'; @Injectable({ providedIn: 'root' @@ -10,10 +12,11 @@ export class RegisterOrgService { private readonly registrationDataKey = 'Registration-Data'; public readonly REGISTER_ORG_NEW_ROUTE = 'register-org-new'; + public readonly CHECK_YOUR_ANSWERS_ROUTE = 'check-your-answers'; - constructor(private readonly sessionStorageService: SessionStorageService) {} + constructor(private readonly sessionStorageService: SessionStorageService, private readonly http: HttpClient) {} - public getRegistrationData() : RegistrationData { + public getRegistrationData(): RegistrationData { const registerOrgStr = this.sessionStorageService.getItem(this.registrationDataKey); // TODO: Remove 'undefined' check once all pages finished if (registerOrgStr && registerOrgStr !== 'undefined') { @@ -21,23 +24,24 @@ export class RegisterOrgService { return registerOrganisation; } return { - name: '', + companyName: '', + companyHouseNumber: null, hasDxReference: null, dxNumber: null, dxExchange: null, services: [], + otherServices: null, hasPBA: null, contactDetails: null, - companyHouseNumber: null, address: null, organisationType: null, otherOrganisationType: null, otherOrganisationDetail: null, - organisationNumber: null, regulatorRegisteredWith: null, regulators: [], hasIndividualRegisteredWithRegulator: null, - individualRegulators: [] + individualRegulators: [], + pbaNumbers: [] } as RegistrationData; } @@ -48,4 +52,9 @@ export class RegisterOrgService { public removeRegistrationData(): void { this.sessionStorageService.removeItem(this.registrationDataKey); } + + public postRegistration(): Observable { + const data = this.getRegistrationData(); + return this.http.post('/external/register-org-new/register', data); + } } diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts index dd9e976ce..b273f2b7d 100644 --- a/src/shared/shared.module.ts +++ b/src/shared/shared.module.ts @@ -47,7 +47,8 @@ import { MonitoringService } from './services/monitoring.service'; ReactiveFormsModule, SuccessIconComponent, SuccessNotificationComponent, - WarningIconComponent + WarningIconComponent, + HmctsErrorSummaryComponent ], providers: [ { diff --git a/test_codecept/accessibility/tests/registerOrgAndOtherOrgs.js b/test_codecept/accessibility/tests/registerOrgAndOtherOrgs.js new file mode 100644 index 000000000..a71e4243d --- /dev/null +++ b/test_codecept/accessibility/tests/registerOrgAndOtherOrgs.js @@ -0,0 +1,56 @@ + +const AppActions = require('../helpers/applicationActions'); +const PallyActions = require('../helpers/pallyActions'); + +const assert = require('assert'); +const { pa11ytest, getResults, initBrowser } = require('../helpers/pa11yUtil'); +const html = require('pa11y-reporter-html'); + +const { conf } = require('../config/config'); + +// const MockApp = require('../../nodeMock/app'); + +describe('Regsiter other orgs', function () { + beforeEach(function (done) { + // MockApp.init(); + done(); + }); + afterEach(async function (done) { + // await MockApp.stopServer(); + done(); + }); + + const registerOrgPages = [ + { route: 'register-org-new/register', cssLocator: 'app-before-you-start' }, + { route: 'register-org-new/company-house-details', cssLocator: 'app-company-house-details' }, + { route: 'register-org-new/organisation-type', cssLocator: 'app-organisation-type' }, + { route: 'register-org-new/regulatory-organisation-type', cssLocator: 'app-regulatory-organisation-type' }, + { route: 'register-org-new/document-exchange-reference', cssLocator: 'app-document-exchange-reference' }, + { route: 'register-org-new/document-exchange-reference-details', cssLocator: 'app-document-exchange-reference-details' }, + { route: 'register-org-new/organisation-services-access', cssLocator: 'app-organisation-services-access' }, + { route: 'register-org-new/payment-by-account', cssLocator: 'app-payment-by-account' }, + { route: 'register-org-new/payment-by-account-details', cssLocator: 'app-payment-by-account-details' }, + { route: 'register-org-new/registered-address/external', cssLocator: 'app-registered-address' }, + { route: 'register-org-new/individual-registered-with-regulator', cssLocator: 'app-individual-registered-with-regulator' }, + { route: 'register-org-new/individual-registered-with-regulator-details', cssLocator: 'app-individual-registered-with-regulator-details' }, + { route: 'register-org-new/registered-regulator', cssLocator: 'app-registered-regulator' }, + { route: 'register-org-new/contact-details', cssLocator: 'app-contact-details' }, + { route: 'register-org-new/registration-submitted', cssLocator: 'app-registration-submitted' }, + { route: 'register-org-new/check-your-answers/true', cssLocator: 'app-check-your-answers' } + + ] + + + registerOrgPages.forEach((pageRoute) => { + it('Route:/' + pageRoute.route, async function () { + await initBrowser(); + const actions = []; + actions.push(...PallyActions.navigateTourl(conf.baseUrl + pageRoute.route)); + actions.push(...PallyActions.waitForPageWithCssLocator(pageRoute.cssLocator)); + await pa11ytest(this, actions); + }); + }) + + +}); + diff --git a/test_codecept/backendMock/app.js b/test_codecept/backendMock/app.js index eb52d526f..ec5272eac 100644 --- a/test_codecept/backendMock/app.js +++ b/test_codecept/backendMock/app.js @@ -14,7 +14,7 @@ const healthRoutes = require('./services/health/routes') const sessionRoutes = require('./services/session/routes') const refDataRoutes = require('./services/refData/routes') const ccdRoutes = require('./services/ccd/routes') - +const acseAssignmentsRoutes = require('./services/caseAssignments/routes') const userApiData = require('./services/userApiData'); class MockApp { @@ -70,6 +70,7 @@ class MockApp { app.use('/health', healthRoutes) app.use('/refdata/external/v1', refDataRoutes) app.use('/ccd', ccdRoutes) + app.use('/case-assignments', acseAssignmentsRoutes ) // await this.stopServer(); diff --git a/test_codecept/backendMock/services/caseAssignments/index.js b/test_codecept/backendMock/services/caseAssignments/index.js new file mode 100644 index 000000000..225b4f5f7 --- /dev/null +++ b/test_codecept/backendMock/services/caseAssignments/index.js @@ -0,0 +1,665 @@ + +const { v4 } = require('uuid'); + +class CCD{ + + constructor(){ + + } + + getCaseTypes(){ + caseData['case_type_id'] = 'Asylum' + return { + "total": 0, "cases": [caseData, caseData], "case_types_results": [ + { case_type_id: 'Asylum', total: 1 }, + { case_type_id: 'Immigration', total: 1 } + ] + }; + } + + getCases(){ + const case1 = getCaseData({ caseId: '1234567812345671', ethosCaseReference:'6042070/2023'}); + const case2 = getCaseData({ caseId: '1234567812345672', ethosCaseReference: '6042071/2023' });; + + return { + headers: caseDataColumnConfigs, + cases: [case1, case2] + } + + } +} + + +module.exports = new CCD(); + + +function getCaseData(data){ + return { + fields: { + "[CASE_REFERENCE]": data && data.caseId ? data.caseId : '1234567812345678', + "ethosCaseReference": data && data.ethosCaseReference ? data.ethosCaseReference : "6042070/2023", + "claimant": "Grayson Becker", + "respondent": "Mrs Test Auto", + "[STATE]": "Accepted", + "case_id": data && data.caseId ? data.caseId : '1234567812345678', + "caseType": "ET_EnglandWales" + }, + case_id: data.caseId + + } +} + + + +const caseDataColumnConfigs = [ + { + fields: [ + { + label: "Case Reference", + case_field_id: '[CASE_REFERENCE]', + case_field_type: { type: 'TEXT' } + + }, + { + label: "Case Number", + case_field_id: 'ethosCaseReference', + case_field_type: { type: 'TEXT' } + + }, + { + label: "Claimant", + case_field_id: 'claimant', + case_field_type: { type: 'TEXT' } + + }, + { + label: "Respondent", + case_field_id: 'respondent', + case_field_type: { type: 'TEXT' } + + }, + { + label: "State", + case_field_id: '[STATE', + case_field_type: { type: 'FixedList' } + + } + ] + } +] + +const orgResponse = { + "name": "Townley Services (TEST) - LaunchDarkly", + "organisationIdentifier": "NPT8F21", + "contactInformation": [ + { + "addressId": "f0bd992b-a1f1-4aef-9935-b28acf26aa67", + "uprn": null, + "created": "2020-04-28T14:59:38.379", + "addressLine1": "28", + "addressLine2": "Sparrowgrove", + "addressLine3": null, + "townCity": "Otterbourne", + "county": "Winchester", + "country": null, + "postCode": "SE6 TU7", + "dxAddress": [ + { + "dxNumber": "DX87744556322", + "dxExchange": "Winchester" + } + ] + } + ], + "status": "ACTIVE", + "sraId": "SRA542467777", + "sraRegulated": false, + "superUser": { + "firstName": "Townley", + "lastName": "Winchester", + "email": "townley.winchester@mailnesia.com" + }, + "paymentAccount": [ + "PBA0356049", + "PBA2445562" + ], + "pendingPaymentAccount": [], + "dateReceived": "2020-04-28T14:59:38.284", + "dateApproved": null +} + + + +const lovRefData = [ + { + "category_key": "HearingType", + "key": "ABA5-BRE", + "value_en": "Breach", + "value_cy": "Torri Amodau", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 4, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FOF", + "value_en": "Finding of Fact", + "value_cy": "Canfod y Ffeithiau", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 12, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FRF", + "value_en": "Financial remedy first appointment", + "value_cy": "Apwyntiad cyntaf rhwymedi ariannol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FRD", + "value_en": "Financial remedy directions", + "value_cy": "Cyfarwyddiadau rhwymedi ariannol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FRI", + "value_en": "Financial remedy interim order", + "value_cy": "Gorchymyn interim rhwymedi ariannol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FRR", + "value_en": "Financial remedy financial dispute resolution", + "value_cy": "Rhwymedi ariannol datrys anghydfod ariannol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FHD", + "value_en": "First Hearing Dispute Resolution Appointment (FHDRA)", + "value_cy": "Apwyntiad Datrys Anghydfod Gwrandawiad Cyntaf (FHDRA)", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 26, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FHR", + "value_en": "First Hearing", + "value_cy": "Gwrandawiad Cyntaf", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 13, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FFH", + "value_en": "Full/Final hearing", + "value_cy": "Gwrandawiad Llawn/Terfynol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 14, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-SGA", + "value_en": "Safeguarding Gatekeeping Appointment", + "value_cy": "Apwyntiad Neilltuo Diogelwch", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 24, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-SCF", + "value_en": "Settlement Conference", + "value_cy": "Cynhadledd Setlo", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 25, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-JMT", + "value_en": "Judgment", + "value_cy": "Dyfarniad", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 19, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-FCM", + "value_en": "Further Case Management Hearing", + "value_cy": "Gwrandawiad Rheoli Achos Pellach", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 15, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-ALL", + "value_en": "Allocation", + "value_cy": "Dyrannu", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 1, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-APP", + "value_en": "Application", + "value_cy": "Cais", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 3, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-APL", + "value_en": "Appeal", + "value_cy": "Apêl", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 2, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-REV", + "value_en": "Review", + "value_cy": "Adolygiad", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 23, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-NEH", + "value_en": "Neutral Evaluation Hearing", + "value_cy": "Gwrandawiad Gwerthusiad Niwtral", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 20, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-IRH", + "value_en": "Issues Resolution Hearing", + "value_cy": "Gwrandawiad Datrys Materion", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-ISO", + "value_en": "Interim Supervision Order", + "value_cy": "Gorchymyn Goruchwylio Interim", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-ICO", + "value_en": "Interim Care Order", + "value_cy": "Gorchymyn Gofal Interim", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-HRA", + "value_en": "Human Rights Act Application", + "value_cy": "Cais dan y Ddeddf Hawliau Dynol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 18, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-DRA", + "value_en": "Dispute Resolution Appointment", + "value_cy": "Apwyntiad Datrys Anghydfod", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 11, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-DIR", + "value_en": "Directions (First/Further)", + "value_cy": "Cyfarwyddiadau (Cyntaf/Pellach)", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 10, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-PRE", + "value_en": "Preliminary (REMO)", + "value_cy": "Rhagarweiniol (REMO)", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-GRH", + "value_en": "Ground Rules Hearing", + "value_cy": "Gwrandawiad rheolau sylfaenol", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 17, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-CHR", + "value_en": "Celebration hearing", + "value_cy": "Gwrandawiad Dathlu", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": null, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-2GA", + "value_en": "2nd Gatekeeping Appointment", + "value_cy": "2il Apwyntiad Neilltuo", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 16, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-PHR", + "value_en": "Pre Hearing Review", + "value_cy": "Adolygiad Cyn Gwrandawiad", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 22, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-COM", + "value_en": "Committal", + "value_cy": "Traddodi", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 7, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-CON", + "value_en": "Conciliation", + "value_cy": "Cymodi", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 8, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-COS", + "value_en": "Costs", + "value_cy": "Costau", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 9, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-PER", + "value_en": "Permission Hearing", + "value_cy": "Gwrandawiad Caniatâd", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 21, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-CMC", + "value_en": "Case Management Conference", + "value_cy": "Cynhadledd Rheoli Achos", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 5, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + }, + { + "category_key": "HearingType", + "key": "ABA5-CMH", + "value_en": "Case Management Hearing", + "value_cy": "Gwrandawiad Rheoli Achos", + "hint_text_en": "", + "hint_text_cy": "", + "lov_order": 6, + "parent_category": "", + "parent_key": "", + "active_flag": "Y", + "child_nodes": null + } +] + + + + +const caseData = { + "id": 123456712345671, + "jurisdiction": "PUBLICLAW", + "state": "Open", + "version": null, + "case_type_id": "CARE_SUPERVISION_EPO", + "created_date": "2023-10-03T12:00:46.471", + "last_modified": "2023-10-03T12:00:49.434", + "last_state_modified_date": "2023-10-03T12:00:46.471", + "security_classification": "PUBLIC", + "case_data": {}, + "data_classification": { + "westLondonDFJCourt": "PUBLIC", + "caseManagementLocation": { + "classification": "PUBLIC", + "value": { + "baseLocation": "PUBLIC", + "region": "PUBLIC" + } + }, + "dfjArea": "PUBLIC", + "taskList": "PUBLIC", + "isLocalAuthority": "PUBLIC", + "caseLocalAuthorityName": "PUBLIC", + "caseLocalAuthority": "PUBLIC", + "court": { + "classification": "PUBLIC", + "value": { + "epimmsId": "PUBLIC", + "code": "PUBLIC", + "regionId": "PUBLIC", + "name": "PUBLIC", + "dateTransferred": "PUBLIC", + "region": "PUBLIC", + "email": "PUBLIC" + } + }, + "caseNameHmctsInternal": "PUBLIC", + "SearchCriteria": { + "classification": "PUBLIC", + "value": {} + }, + "representativeType": "PUBLIC", + "caseManagementCategory": "PUBLIC", + "relatingLA": "PUBLIC", + "caseName": "PUBLIC", + "outsourcingPolicy": { + "classification": "PUBLIC", + "value": { + "OrgPolicyCaseAssignedRole": "PUBLIC", + "Organisation": { + "classification": "PUBLIC", + "value": { + "OrganisationID": "PUBLIC", + "OrganisationName": "PUBLIC" + } + } + } + } + }, + "supplementary_data": { + "orgs_assigned_users": { + "HAUN33E": 1 + }, + "HMCTSServiceId": "ABA3" + }, + "after_submit_callback_response": null, + "callback_response_status_code": null, + "callback_response_status": null, + "delete_draft_response_status_code": null, + "delete_draft_response_status": null +} \ No newline at end of file diff --git a/test_codecept/backendMock/services/caseAssignments/routes.js b/test_codecept/backendMock/services/caseAssignments/routes.js new file mode 100644 index 000000000..6e6eb85fd --- /dev/null +++ b/test_codecept/backendMock/services/caseAssignments/routes.js @@ -0,0 +1,23 @@ + + +const express = require('express') + +const router = express.Router({ mergeParams: true }); +const service = require('./index') + +router.get('/', (req, res) => { + const caseIds = req.query.case_ids + res.send({ case_assignments :[]}) +}); + + + +router.post('/', (req, res) => { + res.send({}) +}); + + + + +module.exports = router; + diff --git a/test_codecept/backendMock/services/ccd/index.js b/test_codecept/backendMock/services/ccd/index.js index d4661fc96..225b4f5f7 100644 --- a/test_codecept/backendMock/services/ccd/index.js +++ b/test_codecept/backendMock/services/ccd/index.js @@ -8,17 +8,22 @@ class CCD{ } getCaseTypes(){ + caseData['case_type_id'] = 'Asylum' return { - "total": 0, "cases": [], "case_types_results": [ - { case_type_id: 'Asylum', total: 1 } + "total": 0, "cases": [caseData, caseData], "case_types_results": [ + { case_type_id: 'Asylum', total: 1 }, + { case_type_id: 'Immigration', total: 1 } ] }; } getCases(){ + const case1 = getCaseData({ caseId: '1234567812345671', ethosCaseReference:'6042070/2023'}); + const case2 = getCaseData({ caseId: '1234567812345672', ethosCaseReference: '6042071/2023' });; + return { headers: caseDataColumnConfigs, - cases: [caseData, caseData] + cases: [case1, case2] } } @@ -27,20 +32,25 @@ class CCD{ module.exports = new CCD(); -const caseData = { - fields: { - "[CASE_REFERENCE]": "1688031324640947", - "ethosCaseReference": "6042070/2023", - "claimant": "Grayson Becker", - "respondent": "Mrs Test Auto", - "[STATE]": "Accepted", - "case_id": "1688031324640947", - "caseType": "ET_EnglandWales" + +function getCaseData(data){ + return { + fields: { + "[CASE_REFERENCE]": data && data.caseId ? data.caseId : '1234567812345678', + "ethosCaseReference": data && data.ethosCaseReference ? data.ethosCaseReference : "6042070/2023", + "claimant": "Grayson Becker", + "respondent": "Mrs Test Auto", + "[STATE]": "Accepted", + "case_id": data && data.caseId ? data.caseId : '1234567812345678', + "caseType": "ET_EnglandWales" + }, + case_id: data.caseId + } - } + const caseDataColumnConfigs = [ { fields: [ @@ -577,3 +587,79 @@ const lovRefData = [ "child_nodes": null } ] + + + + +const caseData = { + "id": 123456712345671, + "jurisdiction": "PUBLICLAW", + "state": "Open", + "version": null, + "case_type_id": "CARE_SUPERVISION_EPO", + "created_date": "2023-10-03T12:00:46.471", + "last_modified": "2023-10-03T12:00:49.434", + "last_state_modified_date": "2023-10-03T12:00:46.471", + "security_classification": "PUBLIC", + "case_data": {}, + "data_classification": { + "westLondonDFJCourt": "PUBLIC", + "caseManagementLocation": { + "classification": "PUBLIC", + "value": { + "baseLocation": "PUBLIC", + "region": "PUBLIC" + } + }, + "dfjArea": "PUBLIC", + "taskList": "PUBLIC", + "isLocalAuthority": "PUBLIC", + "caseLocalAuthorityName": "PUBLIC", + "caseLocalAuthority": "PUBLIC", + "court": { + "classification": "PUBLIC", + "value": { + "epimmsId": "PUBLIC", + "code": "PUBLIC", + "regionId": "PUBLIC", + "name": "PUBLIC", + "dateTransferred": "PUBLIC", + "region": "PUBLIC", + "email": "PUBLIC" + } + }, + "caseNameHmctsInternal": "PUBLIC", + "SearchCriteria": { + "classification": "PUBLIC", + "value": {} + }, + "representativeType": "PUBLIC", + "caseManagementCategory": "PUBLIC", + "relatingLA": "PUBLIC", + "caseName": "PUBLIC", + "outsourcingPolicy": { + "classification": "PUBLIC", + "value": { + "OrgPolicyCaseAssignedRole": "PUBLIC", + "Organisation": { + "classification": "PUBLIC", + "value": { + "OrganisationID": "PUBLIC", + "OrganisationName": "PUBLIC" + } + } + } + } + }, + "supplementary_data": { + "orgs_assigned_users": { + "HAUN33E": 1 + }, + "HMCTSServiceId": "ABA3" + }, + "after_submit_callback_response": null, + "callback_response_status_code": null, + "callback_response_status": null, + "delete_draft_response_status_code": null, + "delete_draft_response_status": null +} \ No newline at end of file diff --git a/test_codecept/backendMock/services/session/index.js b/test_codecept/backendMock/services/session/index.js index b5a735171..3f3c20d77 100644 --- a/test_codecept/backendMock/services/session/index.js +++ b/test_codecept/backendMock/services/session/index.js @@ -105,7 +105,7 @@ class MockSessionService{ } async updateAuthSessionWithRoles(auth, roles){ - await this.waitForSessionWithRoleAssignments(auth) + // await this.waitForSessionWithRoleAssignments(auth) const sessionFile = await this.getSessionFileAuth(auth); let sessionJson = await fs.readFileSync(sessionFile); diff --git a/test_codecept/codeceptCommon/browser.js b/test_codecept/codeceptCommon/browser.js index 4c8634b53..6b9aa89ed 100644 --- a/test_codecept/codeceptCommon/browser.js +++ b/test_codecept/codeceptCommon/browser.js @@ -34,6 +34,8 @@ class DriverManager{ async setCookies(cookies){ for(const cookie of cookies){ + cookie['path'] = '/'; + cookie['domain'] = 'localhost' await getActor().setCookie(cookie) } } diff --git a/test_codecept/codeceptCommon/codecept.conf.ts b/test_codecept/codeceptCommon/codecept.conf.ts index b235905ba..d92a82c5c 100644 --- a/test_codecept/codeceptCommon/codecept.conf.ts +++ b/test_codecept/codeceptCommon/codecept.conf.ts @@ -6,7 +6,7 @@ const fs = require('fs') const path = require('path') const global = require('./globals') -// import applicationServer from '../localServer' +import applicationServer from '../localServer' var spawn = require('child_process').spawn; const backendMockApp = require('../backendMock/app'); @@ -24,8 +24,7 @@ console.log(`parallel : ${parallel}`) console.log(`headless : ${!head}`) -let pipelineBranch = process.env.TEST_URL.includes('pr-') || process.env.TEST_URL.includes('manage-case.aat') ? "preview" : "master" - +let pipelineBranch = process.env.TEST_URL.includes('pr-') ? "preview" : "master" let features = '' if (testType === 'e2e' || testType === 'smoke'){ features = `../e2e/features/app/**/*.feature` @@ -45,14 +44,22 @@ const functional_output_dir = path.resolve(`${__dirname}/../../functional-output const cucumber_functional_output_dir = path.resolve(`${__dirname}/../../functional-output/tests/cucumber-codecept-${testType}`) +const tags = process.env.DEBUG ? 'functional_debug' : 'fullFunctional' + +const grepTags = `(?=.*@${testType === 'smoke' ? 'smoke':tags})^(?!.*@ignore)^(?!.*@${pipelineBranch === 'preview' ? 'AAT_only' : 'preview_only'})` +console.log(grepTags) + exports.config = { timeout: 600, "gherkin": { "features": features, "steps": "../**/*.steps.js" }, + grep: grepTags, output: functional_output_dir, - + // disableScreenshots: false, + // fullPageScreenshots: true, + // uniqueScreenshotNames: true, helpers: { CustomHelper:{ require:"./customHelper.js" @@ -60,44 +67,45 @@ exports.config = { "Mochawesome": { "uniqueScreenshotNames": "true" }, - Puppeteer: { - url: 'https://manage-case.aat.platform.hmcts.net/', - show: true, - waitForNavigation: ['domcontentloaded'], - restart: true, - keepCookies: false, - keepBrowserState: false, - smartWait: 50000, - waitForTimeout: 90000, - chrome: { - ignoreHTTPSErrors: true, - defaultViewport: { - width: 1280, - height: 960 - }, - args: [ - `${head ? '' : '--headless'}`, - '—disable-notifications', - '--smartwait', - '--disable-gpu', - '--no-sandbox', - '--allow-running-insecure-content', - '--ignore-certificate-errors', - '--window-size=1440,1400', - '--viewport-size=1440,1400', - - '--disable-setuid-sandbox', '--no-zygote ', '--disableChecks' - ] - } + // Puppeteer: { + // url: 'https://manage-case.aat.platform.hmcts.net/', + // show: true, + // waitForNavigation: ['domcontentloaded'], + // restart: true, + // keepCookies: false, + // keepBrowserState: false, + // smartWait: 50000, + // waitForTimeout: 90000, + // chrome: { + // ignoreHTTPSErrors: true, + // defaultViewport: { + // width: 1280, + // height: 960 + // }, + // args: [ + // `${head ? '' : '--headless'}`, + // '—disable-notifications', + // '--smartwait', + // '--disable-gpu', + // '--no-sandbox', + // '--allow-running-insecure-content', + // '--ignore-certificate-errors', + // '--window-size=1440,1400', + // '--viewport-size=1440,1400', + + // '--disable-setuid-sandbox', '--no-zygote ', '--disableChecks' + // ] + // } - }, - // Playwright: { - // url: "https://manage-case.aat.platform.hmcts.net", - // restart: false, - // show:true, - // waitForNavigation: "domcontentloaded", - // waitForAction: 500 - // } + // }, + Playwright: { + url: "https://manage-case.aat.platform.hmcts.net", + restart: false, + show: head ? true : false, + waitForNavigation: "domcontentloaded", + waitForAction: 500, + browser: 'chromium' + } // WebDriver:{ // url: 'https://manage-case.aat.platform.hmcts.net/', // browser: 'chrome', @@ -154,7 +162,7 @@ exports.config = { plugins:{ screenshotOnFail: { enabled: true, - fullPageScreenshots: 'true' + fullPageScreenshots: true }, "myPlugin": { @@ -221,7 +229,7 @@ async function setup(){ if (!debugMode && (testType === 'ngIntegration' || testType === 'a11y')){ await backendMockApp.startServer(debugMode); - // await applicationServer.start() + await applicationServer.start() } } @@ -229,7 +237,7 @@ async function setup(){ async function teardown(){ if (!debugMode && (testType === 'ngIntegration' || testType === 'a11y')) { await backendMockApp.stopServer(); - // await applicationServer.stop() + await applicationServer.stop() } statsReporter.run(); await generateCucumberReport(); diff --git a/test_codecept/codeceptCommon/customHelper.js b/test_codecept/codeceptCommon/customHelper.js index ca8b8692e..f105d5b9f 100644 --- a/test_codecept/codeceptCommon/customHelper.js +++ b/test_codecept/codeceptCommon/customHelper.js @@ -21,20 +21,20 @@ class CustomHelper extends Helper { } _beforeStep(){ - const page = this.getPuppeteerPage(); + // const page = this.getPuppeteerPage(); - if (!this.pageOnListener && page){ + // if (!this.pageOnListener && page){ - page.on('console', (msg) => { - const type = msg.type(); - if (type === 'error') { - // console.log(msg); - // this.attachBrowserLog(msg) - this.browserErrorLogs.push(msg) - } - }); - this.pageOnListener = true; - } + // page.on('console', (msg) => { + // const type = msg.type(); + // if (type === 'error') { + // // console.log(msg); + // // this.attachBrowserLog(msg) + // this.browserErrorLogs.push(msg) + // } + // }); + // this.pageOnListener = true; + // } } @@ -89,8 +89,8 @@ class CustomHelper extends Helper { } _getHelper(){ - const { WebDriver, Puppeteer } = this.helpers; - return Puppeteer; + const { WebDriver, Puppeteer, Playwright } = this.helpers; + return Playwright; } @@ -98,8 +98,12 @@ class CustomHelper extends Helper { return this._getHelper(); } + getPlaywrightPage() { + return this.helpers.Playwright.page + } + getPuppeteerPage(){ - return this._getHelper().page; + return this.helpers.Puppeteer.page; } async isVisible(selector) { diff --git a/test_codecept/codeceptCommon/elements.js b/test_codecept/codeceptCommon/elements.js index cf4b6172a..f9675432f 100644 --- a/test_codecept/codeceptCommon/elements.js +++ b/test_codecept/codeceptCommon/elements.js @@ -304,20 +304,31 @@ class Element { return new ElementCollection(locator) } + + + async wait(waitInSec){ - reportLogger.AddMessage("ELEMENT_WAIT: " + JSON.stringify(this.selector) +" at "+this.__getCallingFunctionName()); + await getActor().waitForElement(this.selector, 20) + } + + async playwrightWait(){ + + } + + async puppeteerWait(){ + reportLogger.AddMessage("ELEMENT_WAIT: " + JSON.stringify(this.selector) + " at " + this.__getCallingFunctionName()); return new Promise((resolve, reject) => { const startTime = Date.now(); const thisElement = this; const interval = setInterval(async () => { - const elapsedTime = (Date.now() - startTime)/1000; + const elapsedTime = (Date.now() - startTime) / 1000; const isPresent = await thisElement.isPresent() // reportLogger.AddMessage(`WAIT elapsed time : ${elapsedTime}`) if (isPresent) { clearInterval(interval) resolve(true) - } + } // else if (elapsedTime > 30){ // clearInterval(interval); // reportLogger.AddMessage(`ELEMENT_WAIT_FAILED: not present ${JSON.stringify(this.selector)} at ${this.__getCallingFunctionName()} `); @@ -328,8 +339,8 @@ class Element { setTimeout(() => { clearInterval(interval); reject(false); - }, 30*1000) - + }, 30 * 1000) + }); } diff --git a/test_codecept/codeceptCommon/hooks.js b/test_codecept/codeceptCommon/hooks.js index 4d81b57b5..98773e46b 100644 --- a/test_codecept/codeceptCommon/hooks.js +++ b/test_codecept/codeceptCommon/hooks.js @@ -67,8 +67,11 @@ module.exports = async function () { global.scenarioData = {} output.print(`Test started : ${test.title}`) codeceptMochawesomeLog.AddMessage(`************ Test started : ${test.title}`) - await mockClient.logMessage(`************ Test started : ${test.title}`) featureLogsMessage(test, `\n ************ Test started : ${test.title}`); + if (test.state === 'failed' && process.env.TEST_TYPE !== 'e2e') { + await mockClient.logMessage(`************ Test started : ${test.title}`) + + } statsReporter.run() diff --git a/test_codecept/e2e/features/app/createOrganisation.feature b/test_codecept/e2e/features/app/createOrganisation.feature index fa86cf682..6630e07c5 100644 --- a/test_codecept/e2e/features/app/createOrganisation.feature +++ b/test_codecept/e2e/features/app/createOrganisation.feature @@ -1,4 +1,4 @@ -@fullFunctional +@fullFunctional @AAT_only @functional_debug Feature: Register Organization Background: diff --git a/test_codecept/e2e/features/app/loginLogout.feature b/test_codecept/e2e/features/app/loginLogout.feature index 54b65b15b..863ee9e32 100644 --- a/test_codecept/e2e/features/app/loginLogout.feature +++ b/test_codecept/e2e/features/app/loginLogout.feature @@ -4,13 +4,14 @@ Feature: Login Background: When I navigate to manage organisation Url +@preview_only Scenario: login and log out from manage organisation as ManageOrg user Given I am logged into Townley Services Org Then I should be redirected to manage organisation dashboard page When I select the sign out link Then I should be redirected to the Idam login page - +@AAT_only Scenario: Verify the direct link navigate to login page Given I navigate to manage organisation Url direct link Then I should be redirected back to Login page after direct link diff --git a/test_codecept/e2e/features/app/registerOtherOrg.feature b/test_codecept/e2e/features/app/registerOtherOrg.feature new file mode 100644 index 000000000..e4080b6c3 --- /dev/null +++ b/test_codecept/e2e/features/app/registerOtherOrg.feature @@ -0,0 +1,242 @@ +@fullFunctional @preview_only +Feature: Register other org, registration + + Scenario: register other org workflow with all optional values + Given I navigate to register other org start page + Then I am on register other org page "Apply for an organisation to manage civil, family and tribunal cases" + Then In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I validate fields displayed + | field | + | Before you start | + | I've checked whether my organisation already has an account | + + When In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I input values + | field | value | + | I've checked whether my organisation already has an account | Yes | + When I click start button in before you start page + + Then I am on register other org page "What type of organisation are you registering?" + When In register other org page "What type of organisation are you registering?", I input values + | field | value | + | Select the type of organisation | Solicitor | + + When I click continue in register other org workflow + + Then I am on register other org page "What is your company name and Companies House number?" + When In register other org page "What is your company name and Companies House number?", I input values + | field | value | + | Enter the name of the organisation | Auto test organisation | + | Enter the 8-digit Companies House Number | 12345678 | + When I click continue in register other org workflow + + + Then I am on register other org page "What is the registered address of your organisation?" + When In register other org page "What is the registered address of your organisation?", I input values + | field | value | + | I can't enter a UK postcode | | + # When I click continue in register other org workflow + + Then I am on register other org page "Is this a UK address?" + When In register other org page "Is this a UK address?", I input values + | field | value | + | Is this a UK address? | Yes | + | Building and Street | auto building | + | Address line 2 | auto addr 2 | + | Address line 3 | auto addr 3 | + | Town or City | auto city | + | County | auto country | + | Postcode | SW1V 3BZ | + When I click continue in register other org workflow + + Then I am on register other org page "Do you have a document exchange reference for your main office?" + When In register other org page "Do you have a document exchange reference for your main office?", I input values + | field | value | + | Do you have a document exchange reference for your main office? | Yes | + When I click continue in register other org workflow + + Then I am on register other org page "What's the DX reference for this office?" + When In register other org page "What's the DX reference for this office?", I input values + | field | value | + | DX number | DX12345 | + | DX exchange | HAYES (MIDDLESEX) | + When I click continue in register other org workflow + + + Then I am on register other org page "Who is your organisation registered with?" + When In register other org page "Who is your organisation registered with?", I input values + | field | value | + | Select the type of regulatory organisation | Other | + | Enter the name of the professional body or regulator | test name | + | Enter your organisation's registration number | test number | + + When I click continue in register other org workflow + + Then I am on register other org page "Which services will your organisation need to access?" + When In register other org page "Which services will your organisation need to access?", I input values + | field | value | + | Which services will your organisation need to access? | Divorce,Damages | + + + When I click continue in register other org workflow + + + Then I am on register other org page "Does your organisation have a payment by account number?" + When In register other org page "Does your organisation have a payment by account number?", I input values + | field | value | + | Does your organisation have a payment by account number? | Yes | + When I click continue in register other org workflow + + + Then I am on register other org page "What PBA numbers does your organisation use?" + When In register other org page "What PBA numbers does your organisation use?", I input values + | field | value | + | PBA number | PBA1234567,PBA7654321 | + When I click continue in register other org workflow + + + Then I am on register other org page "Provide your contact details" + When In register other org page "Provide your contact details", I input values + | field | value | + | First name | auto test fn | + | Last name | auto test ln | + | Enter your work email address | auto_test_email@test.com | + When I click continue in register other org workflow + + + Then I am on register other org page "Are you (as an individual) registered with a regulator?" + When In register other org page "Are you (as an individual) registered with a regulator?", I input values + | field | value | + | Are you (as an individual) registered with a regulator? | Yes | + When I click continue in register other org workflow + + + Then I am on register other org page "What regulator are you (as an individual) registered with?" + When In register other org page "What regulator are you (as an individual) registered with?", I input values + | field | value | + | Select the type of regulator | Other | + | Enter the name of the professional body or regulator | Test regulator | + | Enter your organisation's registration number | 12345678 | + When I click continue in register other org workflow + + Then I am on register other org page "Check your answers before you register" + Then In register other org workflow, I validate check your answers displayed + | field | value | + | Organisation type | Solicitor | + | Organisation name | Auto test organisation | + | Company registration number | 12345678 | + | Organisation address | auto building,auto addr 2,auto addr 3,auto city,auto country,SW1V 3BZ,UK | + | DX reference | DX12345, HAYES (MIDDLESEX) | + | Service to access | Divorce,Damages | + | PBA number(s) | PBA1234567,PBA7654321 | + | Regulatory organisation type | Other: test name ref: test number | + | First name(s) | auto test fn | + | Last name | auto test ln | + | Email address | auto_test_email@test.com | + | What regulators are you (as an individual) registered with? | Other: Test regulator ref: 12345678 | + + + Scenario: register other org workflow with minimum values + Given I navigate to register other org start page + Then I am on register other org page "Apply for an organisation to manage civil, family and tribunal cases" + Then In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I validate fields displayed + | field | + | Before you start | + | I've checked whether my organisation already has an account | + + When In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I input values + | field | value | + | I've checked whether my organisation already has an account | Yes | + When I click start button in before you start page + + Then I am on register other org page "What type of organisation are you registering?" + When In register other org page "What type of organisation are you registering?", I input values + | field | value | + | Select the type of organisation | Solicitor | + + When I click continue in register other org workflow + + Then I am on register other org page "What is your company name and Companies House number?" + When In register other org page "What is your company name and Companies House number?", I input values + | field | value | + | Enter the name of the organisation | Auto test organisation | + When I click continue in register other org workflow + + + Then I am on register other org page "What is the registered address of your organisation?" + When In register other org page "What is the registered address of your organisation?", I input values + | field | value | + | I can't enter a UK postcode | | + # When I click continue in register other org workflow + + Then I am on register other org page "Is this a UK address?" + When In register other org page "Is this a UK address?", I input values + | field | value | + | Is this a UK address? | No | + | Building and Street | auto building | + | Town or City | auto city | + | Country | Ireland | + When I click continue in register other org workflow + + Then I am on register other org page "Do you have a document exchange reference for your main office?" + When In register other org page "Do you have a document exchange reference for your main office?", I input values + | field | value | + | Do you have a document exchange reference for your main office? | No | + When I click continue in register other org workflow + + + Then I am on register other org page "Who is your organisation registered with?" + When In register other org page "Who is your organisation registered with?", I input values + | field | value | + | Select the type of regulatory organisation | Solicitor Regulation Authority (SRA) | + | Enter your organisation's registration number | 12345678 | + + When I click continue in register other org workflow + + Then I am on register other org page "Which services will your organisation need to access?" + When In register other org page "Which services will your organisation need to access?", I input values + | field | value | + | Which services will your organisation need to access? | Divorce,Damages | + + + When I click continue in register other org workflow + + + Then I am on register other org page "Does your organisation have a payment by account number?" + When In register other org page "Does your organisation have a payment by account number?", I input values + | field | value | + | Does your organisation have a payment by account number? | No | + When I click continue in register other org workflow + + + Then I am on register other org page "Provide your contact details" + When In register other org page "Provide your contact details", I input values + | field | value | + | First name | auto test fn | + | Last name | auto test ln | + | Enter your work email address | auto_test_email@test.com | + When I click continue in register other org workflow + + + Then I am on register other org page "Are you (as an individual) registered with a regulator?" + When In register other org page "Are you (as an individual) registered with a regulator?", I input values + | field | value | + | Are you (as an individual) registered with a regulator? | No | + When I click continue in register other org workflow + + + Then I am on register other org page "Check your answers before you register" + Then In register other org workflow, I validate check your answers displayed + | field | value | + | Organisation type | Solicitor | + | Organisation name | Auto test organisation | + | Organisation address | auto building,auto city,Irelan | + | Service to access | Divorce,Damages | + | Regulatory organisation type | Solicitor Regulation Authority (SRA) ref: 12345678 | + | First name(s) | auto test fn | + | Last name | auto test ln | + | Email address | auto_test_email@test.com | + + Then In register other org workflow, I validate check your answers not displays fields + | field | + | DX reference | + | DX number | + diff --git a/test_codecept/e2e/features/app/registerOtherOrgNavigation.feature b/test_codecept/e2e/features/app/registerOtherOrgNavigation.feature new file mode 100644 index 000000000..2326d8371 --- /dev/null +++ b/test_codecept/e2e/features/app/registerOtherOrgNavigation.feature @@ -0,0 +1,203 @@ + +@fullFunctional @preview_only +Feature: Register other org, Navigations + + Background: rgister org fill pages + Given I navigate to register other org start page + Then I am on register other org page "Apply for an organisation to manage civil, family and tribunal cases" + Then In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I validate fields displayed + | field | + | Before you start | + | I've checked whether my organisation already has an account | + + When In register other org page "Apply for an organisation to manage civil, family and tribunal cases", I input values + | field | value | + | I've checked whether my organisation already has an account | Yes | + When I click start button in before you start page + + Then I am on register other org page "What type of organisation are you registering?" + When In register other org page "What type of organisation are you registering?", I input values + | field | value | + | Select the type of organisation | Solicitor | + + When I click continue in register other org workflow + + Then I am on register other org page "What is your company name and Companies House number?" + When In register other org page "What is your company name and Companies House number?", I input values + | field | value | + | Enter the name of the organisation | Auto test organisation | + | Enter the 8-digit Companies House Number | 12345678 | + When I click continue in register other org workflow + + + Then I am on register other org page "What is the registered address of your organisation?" + When In register other org page "What is the registered address of your organisation?", I input values + | field | value | + | I can't enter a UK postcode | | + # When I click continue in register other org workflow + + Then I am on register other org page "Is this a UK address?" + When In register other org page "Is this a UK address?", I input values + | field | value | + | Is this a UK address? | Yes | + | Building and Street | auto building | + | Address line 2 | auto addr 2 | + | Address line 3 | auto addr 3 | + | Town or City | auto city | + | County | auto country | + | Postcode | SW1V 3BZ | + When I click continue in register other org workflow + + Then I am on register other org page "Do you have a document exchange reference for your main office?" + When In register other org page "Do you have a document exchange reference for your main office?", I input values + | field | value | + | Do you have a document exchange reference for your main office? | Yes | + When I click continue in register other org workflow + + Then I am on register other org page "What's the DX reference for this office?" + When In register other org page "What's the DX reference for this office?", I input values + | field | value | + | DX number | DX12345 | + | DX exchange | HAYES (MIDDLESEX) | + When I click continue in register other org workflow + + + Then I am on register other org page "Who is your organisation registered with?" + When In register other org page "Who is your organisation registered with?", I input values + | field | value | + | Select the type of regulatory organisation | Other | + | Enter the name of the professional body or regulator | test name | + | Enter your organisation's registration number | test number | + + When I click continue in register other org workflow + + Then I am on register other org page "Which services will your organisation need to access?" + When In register other org page "Which services will your organisation need to access?", I input values + | field | value | + | Which services will your organisation need to access? | Divorce,Damages | + + + When I click continue in register other org workflow + + + Then I am on register other org page "Does your organisation have a payment by account number?" + When In register other org page "Does your organisation have a payment by account number?", I input values + | field | value | + | Does your organisation have a payment by account number? | Yes | + When I click continue in register other org workflow + + + Then I am on register other org page "What PBA numbers does your organisation use?" + When In register other org page "What PBA numbers does your organisation use?", I input values + | field | value | + | PBA number | PBA1234567,PBA7654321 | + When I click continue in register other org workflow + + + Then I am on register other org page "Provide your contact details" + When In register other org page "Provide your contact details", I input values + | field | value | + | First name | auto test fn | + | Last name | auto test ln | + | Enter your work email address | auto_test_email@test.com | + When I click continue in register other org workflow + + + Then I am on register other org page "Are you (as an individual) registered with a regulator?" + When In register other org page "Are you (as an individual) registered with a regulator?", I input values + | field | value | + | Are you (as an individual) registered with a regulator? | Yes | + When I click continue in register other org workflow + + + Then I am on register other org page "What regulator are you (as an individual) registered with?" + When In register other org page "What regulator are you (as an individual) registered with?", I input values + | field | value | + | Select the type of regulator | Other | + | Enter the name of the professional body or regulator | Test regulator | + | Enter your organisation's registration number | 12345678 | + When I click continue in register other org workflow + + Then I am on register other org page "Check your answers before you register" + +@ignore + Scenario: workflow with all optional values + When In register other org work flow, I click back link + Then I am on register other org page "What regulator are you (as an individual) registered with?" + + When In register other org work flow, I click back link + Then I am on register other org page "Are you (as an individual) registered with a regulator?" + + When In register other org work flow, I click back link + Then I am on register other org page "Provide your contact details" + + When In register other org work flow, I click back link + Then I am on register other org page "What PBA numbers does your organisation use?" + + When In register other org work flow, I click back link + Then I am on register other org page "Does your organisation have a payment by account number?" + + When In register other org work flow, I click back link + Then I am on register other org page "Which services will your organisation need to access?" + + When In register other org work flow, I click back link + Then I am on register other org page "Who is your organisation registered with?" + + When In register other org work flow, I click back link + Then I am on register other org page "What's the DX reference for this office?" + + When In register other org work flow, I click back link + Then I am on register other org page "Do you have a document exchange reference for your main office?" + + When In register other org work flow, I click back link + Then I am on register other org page "Is this a UK address?" + + When In register other org work flow, I click back link + Then I am on register other org page "What is the registered address of your organisation?" + + + When In register other org work flow, I click back link + Then I am on register other org page "What is your company name and Companies House number?" + + + When In register other org work flow, I click back link + Then I am on register other org page "What type of organisation are you registering?" + + When In register other org work flow, I click back link + Then I am on register other org page "Apply for an organisation to manage civil, family and tribunal cases" + + + Scenario: check your answers, chnage links + Then In register other org workflow, I validate change links + | field | screen | + | Organisation type | What type of organisation are you registering? | + | Organisation name | What is your company name and Companies House number? | + | Company registration number | What is your company name and Companies House number? | + | Organisation address | What is the registered address of your organisation? | + | DX reference | What's the DX reference for this office? | + | Service to access | Which services will your organisation need to access? | + | PBA number(s) | Does your organisation have a payment by account number? | + | Regulatory organisation type | Who is your organisation registered with? | + | First name(s) | Provide your contact details | + | Last name | Provide your contact details | + | Email address | Provide your contact details | + | What regulators are you (as an individual) registered with? | What regulator are you (as an individual) registered with? | + + Scenario: check your answers, chnage link and continue + When In register other org check your answers page, I click change link for field "Organisation type" + Then I am on register other org page "What type of organisation are you registering?" + Then In register other org workflow, I validate continue pages + | page | + | What is your company name and Companies House number? | + | What is the registered address of your organisation? | + | Do you have a document exchange reference for your main office? | + | What's the DX reference for this office? | + | Who is your organisation registered with? | + | Which services will your organisation need to access? | + | Does your organisation have a payment by account number? | + | What PBA numbers does your organisation use? | + | Provide your contact details | + | Are you (as an individual) registered with a regulator? | + | What regulator are you (as an individual) registered with? | + | Check your answers before you register | + diff --git a/test_codecept/e2e/features/app/registerOtherOrgPageValidations.feature b/test_codecept/e2e/features/app/registerOtherOrgPageValidations.feature new file mode 100644 index 000000000..e54bf65bb --- /dev/null +++ b/test_codecept/e2e/features/app/registerOtherOrgPageValidations.feature @@ -0,0 +1,201 @@ +@fullFunctional @preview_only +Feature: Register other org, page validations + + + Scenario: Register other org, page level validations in What type of organisation are you registering? + When In register organisation workflow, I navigate to route "organisation-type" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select an organisation | + + When In register other org page "What type of organisation are you registering?", I input values + | field | value | + | Select the type of organisation | Solicitor | + When I click continue in register other org workflow + Then I am on register other org page "What is your company name and Companies House number?" + + + Scenario: Register other org, page level validations in What is your company name and Companies House number? + When In register organisation workflow, I navigate to route "company-house-details" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter an organisation name | + + When In register other org page "What is your company name and Companies House number?", I input values + | field | value | + | Enter the name of the organisation | Auto test organisation | + When I click continue in register other org workflow + Then I am on register other org page "What is the registered address of your organisation?" + + + Scenario: Register other org, page level validations in What is the registered address of your organisation? + When In register organisation workflow, I navigate to route "registered-address/external" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter a valid postcode | + + When In register other org page "What is the registered address of your organisation?", I input values + | field | value | + | Provide address details | SW1V 3BZ,Flat 1 | + When I click continue in register other org workflow + + + Scenario: Register other org, page level validations in Does your organisation have a payment by account number? + When In register organisation workflow, I navigate to route "payment-by-account" + Then I am on register other org page "Does your organisation have a payment by account number?" + + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select an option | + + When In register other org page "Does your organisation have a payment by account number?", I input values + | field | value | + | Does your organisation have a payment by account number? | yes | + When I click continue in register other org workflow + Then I am on register other org page "What PBA numbers does your organisation use?" + + + Scenario: Register other org, page level validations in Who is your organisation registered with? + When In register organisation workflow, I navigate to route "regulatory-organisation-type" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select a regulatory organisation | + + When In register other org page "Who is your organisation registered with?", I input values + | field | value | + | Select the type of regulatory organisation | Solicitor Regulation Authority (SRA) | + When I click continue in register other org workflow + + + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter a registration number | + When In register other org page "Who is your organisation registered with?", I input values + | field | value | + | Enter your organisation's registration number | SRA12345678 | + + + When I click continue in register other org workflow + Then I am on register other org page "Which services will your organisation need to access?" + + + + Scenario: Register other org, page level validations in Which services will your organisation need to access? + When In register organisation workflow, I navigate to route "organisation-services-access" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select at least one service | + + When In register other org page "Which services will your organisation need to access?", I input values + | field | value | + | Which services will your organisation need to access? | Damages | + When I click continue in register other org workflow + Then I am on register other org page "Does your organisation have a payment by account number?" + + + Scenario: Register other org, page level validations in Does your organisation have a payment by account number? + When In register organisation workflow, I navigate to route "payment-by-account" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select an option | + + When In register other org page "Does your organisation have a payment by account number?", I input values + | field | value | + | Does your organisation have a payment by account number? | Yes | + When I click continue in register other org workflow + Then I am on register other org page "What PBA numbers does your organisation use?" + +@ignore + Scenario: Register other org, page level validations in What PBA numbers does your organisation use? + When In register organisation workflow, I navigate to route "payment-by-account-details" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please enter PBA number | + + When In register other org page "What PBA numbers does your organisation use?", I input values + | field | value | + | PBA number | PBA1234567 | + When I click continue in register other org workflow + Then I am on register other org page "What PBA numbers does your organisation use?" + + + Scenario: Register other org, page level validations in Provide your contact details + When In register organisation workflow, I navigate to route "contact-details" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter first name | + | Enter last name | + | Enter email address | + + When In register other org page "Provide your contact details", I input values + | field | value | + | First name | testfn | + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter last name | + | Enter email address | + + When In register other org page "Provide your contact details", I input values + | field | value | + | Last name | testln | + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter email address | + + When In register other org page "Provide your contact details", I input values + | field | value | + | Enter your work email address | test@test.com | + When I click continue in register other org workflow + + + Then I am on register other org page "Are you (as an individual) registered with a regulator?" + + + Scenario: Register other org, page level validations in Are you (as an individual) registered with a regulator? + When In register organisation workflow, I navigate to route "individual-registered-with-regulator" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select an option | + + When In register other org page "Are you (as an individual) registered with a regulator?", I input values + | field | value | + | Are you (as an individual) registered with a regulator? | Yes | + When I click continue in register other org workflow + Then I am on register other org page "What regulator are you (as an individual) registered with?" + + + + + Scenario: Register other org, page level validations in What regulator are you (as an individual) registered with? + When In register organisation workflow, I navigate to route "individual-registered-with-regulator-details" + When I click continue in register other org workflow + Then In register organisation workflow, I validate error messages displayed + | message | + | Please select a regulatory organisation | + + When In register other org page "What regulator are you (as an individual) registered with?", I input values + | field | value | + | Select the type of regulator | Solicitor Regulation Authority (SRA) | + When I click continue in register other org workflow + + Then In register organisation workflow, I validate error messages displayed + | message | + | Enter a registration number | + + When In register other org page "What regulator are you (as an individual) registered with?", I input values + | field | value | + | Enter your organisation's registration number |SRA12345678 | + When I click continue in register other org workflow + Then I am on register other org page "Check your answers before you register" \ No newline at end of file diff --git a/test_codecept/e2e/features/app/unassignedCases.feature b/test_codecept/e2e/features/app/unassignedCases.feature new file mode 100644 index 000000000..7dd085623 --- /dev/null +++ b/test_codecept/e2e/features/app/unassignedCases.feature @@ -0,0 +1,11 @@ + +@functional_debug +Feature: Unassigned cases tab + + Scenario: page validation + When I navigate to manage organisation Url + Given I am logged into Townley Services Org + + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page \ No newline at end of file diff --git a/test_codecept/e2e/features/pageObjects/headerPage.js b/test_codecept/e2e/features/pageObjects/headerPage.js index 63901f143..3c7a493dc 100644 --- a/test_codecept/e2e/features/pageObjects/headerPage.js +++ b/test_codecept/e2e/features/pageObjects/headerPage.js @@ -12,6 +12,9 @@ class HeaderPage { this.user = element(by.xpath('//*[contains(@class, \'hmcts-primary-navigation__link\') and text() = \'Users\']')); this.feeAccounts = element(by.xpath('//*[contains(@class, \'hmcts-primary-navigation__link\') and text() = \'Fee Accounts\']')); + this.unassigedCasesTab = element(by.xpath('//*[contains(@class, \'hmcts-primary-navigation__link\') and text() = \'Unassigned cases\']')); + this.assignedCasesTab = element(by.xpath('//*[contains(@class, \'hmcts-primary-navigation__link\') and text() = \'Assigned cases\']')); + this.inviteUser = element(by.css('[class=\'govuk-button hmcts-page-heading__button\']')); this.back = element(by.xpath('//a[contains(text(),\'Back\')]')); this.signOut = element(by.xpath('//a[contains(text(),\'Sign out\')]')); @@ -75,6 +78,28 @@ class HeaderPage { // browser.sleep(AMAZING_DELAY); } + async clickUnassignedCases() { + await BrowserWaits.waitForCondition(async () => { + const isSpinnerPresent = await this.spinner.isPresent(); + console.log('Spinner present status : ' + isSpinnerPresent); + return !isSpinnerPresent; + }); + await BrowserWaits.waitForElement(this.unassigedCasesTab); + await this.clickHeaderTab(this.unassigedCasesTab); + // browser.sleep(AMAZING_DELAY); + } + + async clickAssignedCases() { + await BrowserWaits.waitForCondition(async () => { + const isSpinnerPresent = await this.spinner.isPresent(); + console.log('Spinner present status : ' + isSpinnerPresent); + return !isSpinnerPresent; + }); + await BrowserWaits.waitForElement(this.assignedCasesTab); + await this.clickHeaderTab(this.assignedCasesTab); + // browser.sleep(AMAZING_DELAY); + } + async clickHeaderTab(tabElement) { let clickSuccess = false; let counter = 0; diff --git a/test_codecept/e2e/features/pageObjects/registerOrgWorkFlow.js b/test_codecept/e2e/features/pageObjects/registerOrgWorkFlow.js new file mode 100644 index 000000000..62aaff649 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOrgWorkFlow.js @@ -0,0 +1,307 @@ +const { timeStamp } = require("console"); + +class RegsiterOrgWorkFlow{ + constructor(){ + + this.continueBtn = $(''); + this.previoudBtn = $(''); + this.cancelLink = $(''); + this.backLink = $(''); + + this.pages = { + "Before you start": new BeforeYouStartPage(), + "": new OrganisationTypePage(), + "": new RegulatoryOrganisationTypePage(), + "": new CompanyHouseDetailsComponent(), + "": new DocumentExchangeReferencePage(), + "": new DocumentExchangeReferenceDetailsPage(), + "": new OfficeAddressPage(), + "": new OrganisationServicesPage(), + "": new PaymentByAccountPage(), + "": new PaymentByAccountDetailsPage(), + "": new RegisteredAddressPage(), + "": new RegisteredAddressPage(), + "": new RegisteredRegulatorPage(), + "": new RegisteredWithRegulatorPage(), + "": new ContactDetailsPage(), + "": new RegistrationSubmittedPage(), + "": new CheckYourAnswersPage() + } + } + + async clickContinueBtn() { + this.continueBtn.click(); + } + + async clickPreviousBtn() { + this.previoudBtn.click() + } + + async clickCancelLink() { + this.cancelLink.click() + } + + async clickBackLink() { + this.backLink.click() + } + + getWorkflowPageObject(page){ + let pageObject = this.pages[page]; + if (pageObject === null || pageObject === undefined){ + throw new Error(`test error: Page object ${page} is not found in workflow. `) + } + + return pageObject; + } + + async isPageDisplayed(page){ + const pageObject = this.getWorkflowPageObject(page); + return await pageObject.container.isDisplayed(); + } + + async validatePage(page){ + const pageObject = this.getWorkflowPageObject(page); + await pageObject.validatePage(); + } +} + + +class BeforeYouStartPage{ + constructor(){ + this.checkbox = $(''); + + this.fieldMapping = { + + } + } + + async inputValue(field, value){ + + } + + +} + + +class OrganisationTypePage { + constructor() { + this.container = $(''); + + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class RegulatoryOrganisationTypePage { + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + +} + +class CompanyHouseDetailsComponent{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class DocumentExchangeReferencePage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class DocumentExchangeReferenceDetailsPage { + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class OfficeAddressPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + +} + +class OrganisationServicesPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class PaymentByAccountPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + +} + +class PaymentByAccountDetailsPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class RegisteredAddressPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + +} + +class RegisteredRegulatorPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class RegisteredWithRegulatorPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class ContactDetailsPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + +class RegistrationSubmittedPage{ + constructor() { + this.container = $(''); + } + + async enterDetails(detailsObj) { + + } + + +} + +class CheckYourAnswersPage{ + constructor() { + this.container = $(''); + this.fieldMapping = { + + } + } + + async inputValue(field, value) { + + } + + +} + + +module.exports = new RegsiterOrgWorkFlow() + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/beforeYouStartPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/beforeYouStartPage.js new file mode 100644 index 000000000..6208f8bc8 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/beforeYouStartPage.js @@ -0,0 +1,28 @@ + + +class BeforeYouStartPage{ + + constructor(){ + this.container = $('app-before-you-start') + + this.fieldMapping = { + "Before you start": element(by.xpath(`//h3[contains(text(),'Before you start')]`)), + "I've checked whether my organisation already has an account": $(`#confirmed-organisation-account`) + } + } + + async inputValue(field, value){ + switch(field){ + case "I've checked whether my organisation already has an account": + await this.fieldMapping[field].click(); + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + + +} + +module.exports = BeforeYouStartPage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/checkYourAnswersPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/checkYourAnswersPage.js new file mode 100644 index 000000000..0ebe93e5d --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/checkYourAnswersPage.js @@ -0,0 +1,70 @@ +const reportLogger = require("../../../../codeceptCommon/reportLogger"); + +class CheckYourAnswersPage{ + + constructor(){ + this.container = $('app-check-your-answers') + } + + + + async isDisplayed() { + return await this.pageContainer.isDisplayed() + } + + async clickChangeLinkForField(field) { + const fieldLocators = this.getRowElementLocators(field); + const changeLinkForField = element(by.xpath(fieldLocators.changeLinkElement)) + await changeLinkForField.click(); + } + + getRowElementLocators(field) { + + let nameELement = null; + let valueElement = null; + let changeLinkElement = null; + + switch(field){ + case 'Regulatory organisation type': + case 'What regulators are you (as an individual) registered with?': + nameELement = `//dl[contains(@class,'govuk-summary-list')]//span[contains(text(),'${field}')]`; + valueElement = `//dl[contains(@class,'govuk-summary-list')]//span[contains(text(),'${field}')]/../../dd[contains(@class,'govuk-summary-list__value')]//span`; + changeLinkElement = `//dl[contains(@class,'govuk-summary-list')]//span[contains(text(),'${field}')]/../../dd[contains(@class,'govuk-summary-list__actions')]/a`; + break; + default: + nameELement = `//div[contains(@class,'govuk-summary-list')]//dt[contains(@class,'govuk-summary-list__key')][contains(text(),'${field}')]`; + valueElement = `//div[contains(@class,'govuk-summary-list')]//dt[contains(@class,'govuk-summary-list__key')][contains(text(),'${field}')]/../dd[contains(@class,'govuk-summary-list__value')]`; + changeLinkElement = `//div[contains(@class,'govuk-summary-list')]//dt[contains(@class,'govuk-summary-list__key')][contains(text(),'${field}')]/../dd[contains(@class,'govuk-summary-list__actions')]/a`; + } + + return { + nameElement: nameELement, + valueElement: valueElement, + changeLinkElement: changeLinkElement + } + } + + async validateSummaryFieldWithValueDisplayed(field, value) { + + let fieldLevelLocators = this.getRowElementLocators(field) + await reportLogger.AddMessage(`${JSON.stringify(fieldLevelLocators, null,2)}`); + expect(await element(by.xpath(fieldLevelLocators.nameElement)).isDisplayed(), `field ${field} not displayed`).to.be.true + const valuesList = value.split(','); + for (let val of valuesList) { + expect(await element(by.xpath(fieldLevelLocators.valueElement)).getText(), `field ${field} value ${val} not included`).includes(val.trim()) + } + expect(await element(by.xpath(fieldLevelLocators.changeLinkElement)).isDisplayed(), `case field ${field} change link not displayed`).to.be.true + } + + async validateSummaryFieldNotDisplayed(field) { + + let fieldLevelLocators = this.getRowElementLocators(field) + await reportLogger.AddMessage(`${JSON.stringify(fieldLevelLocators, null, 2)}`); + expect(await element(by.xpath(fieldLevelLocators.nameElement)).isDisplayed(), `field ${field} is displayed`).to.be.false + + } + +} + +module.exports = CheckYourAnswersPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/companyHouseDetailsPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/companyHouseDetailsPage.js new file mode 100644 index 000000000..3f37933d9 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/companyHouseDetailsPage.js @@ -0,0 +1,31 @@ + + +class CompanyHouseDetailsPage { + + constructor() { + this.container = $('app-company-house-details') + + this.fieldMapping = { + 'Enter the name of the organisation': $('#company-name'), + 'Enter the 8-digit Companies House Number': $('#company-house-number') + } + } + + async inputValue(field, value) { + switch (field) { + case 'Enter the name of the organisation': + await this.fieldMapping[field].sendKeys(value) + break; + case 'Enter the 8-digit Companies House Number': + await this.fieldMapping[field].sendKeys(value) + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + + +} + +module.exports = CompanyHouseDetailsPage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/contactDetailsPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/contactDetailsPage.js new file mode 100644 index 000000000..2231265d8 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/contactDetailsPage.js @@ -0,0 +1,33 @@ + +const browserWaits = require('../../../support/customWaits') +class ContactDetailsPage { + + constructor() { + this.container = $('app-contact-details') + + this.fieldMapping = { + 'First name': $("#first-name"), + 'Last name': $('#last-name'), + "Enter your work email address": $('#work-email-address') + } + } + + async inputValue(field, value) { + switch (field) { + case 'First name': + case 'Last name': + case 'Enter your work email address': + await browserWaits.waitForElement(this.fieldMapping[field]) + await this.fieldMapping[field].sendKeys(value) + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = ContactDetailsPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferenceDetailsPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferenceDetailsPage.js new file mode 100644 index 000000000..6c15b468c --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferenceDetailsPage.js @@ -0,0 +1,32 @@ + + +class DocumentExchangeReferenceDetailsPage { + + constructor() { + this.container = $('app-document-exchange-reference-details') + + + + this.fieldMapping = { + 'DX number': $("#dx-number"), + 'DX exchange': $('#dx-exchange') + } + } + + async inputValue(field, value) { + switch (field) { + case 'DX number': + case 'DX exchange': + await this.fieldMapping[field].sendKeys(value) + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = DocumentExchangeReferenceDetailsPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferencePage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferencePage.js new file mode 100644 index 000000000..5fdbc8c28 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/documentExchangeReferencePage.js @@ -0,0 +1,36 @@ + + +class DocumentExchangeReferencePage { + + constructor() { + this.container = $('app-document-exchange-reference') + + this.radioYes = $('#document-exchange-yes') + this.radioNo = $('#document-exchange-no') + + + this.fieldMapping = { + 'Do you have a document exchange reference for your main office?': $("input[name='documentExchange']") + } + } + + async inputValue(field, value) { + switch (field) { + case 'Do you have a document exchange reference for your main office?': + if(value.toLowerCase().trim().includes('yes')){ + await this.radioYes.click() + }else{ + await this.radioNo.click() + } + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = DocumentExchangeReferencePage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorDetailsPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorDetailsPage.js new file mode 100644 index 000000000..9e8f25c79 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorDetailsPage.js @@ -0,0 +1,42 @@ + +const browserWaits = require('../../../support/customWaits') + +class IndividualRegisteredWithRegulatorDetailsPage { + + constructor() { + this.container = $('app-individual-registered-with-regulator-details') + + + + this.fieldMapping = { + 'Select the type of regulator': $('select[name="regulatorType"]'), + 'Enter the name of the professional body or regulator': $('input[name="regulatorName"]'), + 'Enter your organisation\'s registration number': $('input[name="organisationRegistrationNumber"]') + } + } + + async inputValue(field, value) { + switch (field) { + case 'Select the type of regulator': + await this.waitForREgulatorTypeOption(value.trim()) + await this.fieldMapping[field].select(value.trim()) + break; + case "Enter the name of the professional body or regulator": + case "Enter your organisation's registration number": + await this.fieldMapping[field].sendKeys(value.trim()) + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + + async waitForREgulatorTypeOption(option) { + const ele = element(by.xpath(`//select[@name='regulatorType']//option[contains(text(),'${option}')]`)) + await browserWaits.waitForElement(ele) + } + + +} + +module.exports = IndividualRegisteredWithRegulatorDetailsPage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorPage.js new file mode 100644 index 000000000..7cbe2a477 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/individualRegisteredWithRegulatorPage.js @@ -0,0 +1,36 @@ + + +class IndividualRegisteredWithRegulatorPage { + + constructor() { + this.container = $('app-individual-registered-with-regulator') + + this.radioYes = $('#registered-with-regulator-yes') + this.radioNo = $('#registered-with-regulator-no') + + + this.fieldMapping = { + 'Are you (as an individual) registered with a regulator?': $("input[name='registeredWithRegulator']") + } + } + + async inputValue(field, value) { + switch (field) { + case 'Are you (as an individual) registered with a regulator?': + if (value.toLowerCase().trim().includes('yes')) { + await this.radioYes.click() + } else { + await this.radioNo.click() + } + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = IndividualRegisteredWithRegulatorPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationServicesAccessPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationServicesAccessPage.js new file mode 100644 index 000000000..d8027331f --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationServicesAccessPage.js @@ -0,0 +1,31 @@ + + +class OrganisationServicesAccessPage { + + constructor() { + this.container = $('app-organisation-services-access') + + this.fieldMapping = { + "Which services will your organisation need to access?": element(by.xpath(`//div[contains(text(),'Select services to access')]`)) + } + } + + async inputValue(field, value) { + switch (field) { + case "Which services will your organisation need to access?": + const services = value.split(',') + for (const service of services){ + const ele = element(by.xpath(`//label[contains(text(),'${service.trim()}')]/../input[@name='services']`)) + await ele.click(); + } + + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = OrganisationServicesAccessPage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationTypePage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationTypePage.js new file mode 100644 index 000000000..7a3fd0416 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/organisationTypePage.js @@ -0,0 +1,27 @@ + + +class OrganisationTypePage { + + constructor() { + this.container = $('app-organisation-type') + + this.fieldMapping = { + "Select the type of organisation": element(by.xpath(`//h1[contains(text(),'Select the type of organisation')]`)) + } + } + + async inputValue(field, value) { + switch (field) { + case "Select the type of organisation": + const ele = element(by.xpath(`//label[contains(text(),'${value}')]/../input`)) + await ele.click(); + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = OrganisationTypePage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountDetailsPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountDetailsPage.js new file mode 100644 index 000000000..e97dadfdc --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountDetailsPage.js @@ -0,0 +1,39 @@ + + +class PaymentByAccountDetailsPage { + + constructor() { + this.container = $('app-payment-by-account-details') + + this.fieldMapping = { + 'PBA number': $("input#pba-number-0") + } + } + + async inputValue(field, value) { + switch (field) { + case 'PBA number': + const pbaList = value.split(',') + + let pbaCounter = 0; + for (const pba of pbaList){ + if (pbaCounter !== 0){ + const addAnotherBtn = element(by.xpath(`//button[contains(text(),'Add another PBA number')]`)) + await addAnotherBtn.click() + } + const ele = $(`input#pba-number-${pbaCounter}`) + await ele.sendKeys(pba.trim()) + pbaCounter++; + } + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = PaymentByAccountDetailsPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountPage.js new file mode 100644 index 000000000..5609c1c08 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/paymentByAccountPage.js @@ -0,0 +1,36 @@ + + +class PaymentByAccountPage { + + constructor() { + this.container = $('app-payment-by-account') + + this.radioYes = $('#pba-yes') + this.radioNo = $('#pba-no') + + + this.fieldMapping = { + 'Does your organisation have a payment by account number?': $("input[name='pba']") + } + } + + async inputValue(field, value) { + switch (field) { + case 'Does your organisation have a payment by account number?': + if (value.toLowerCase().trim().includes('yes')) { + await this.radioYes.click() + } else { + await this.radioNo.click() + } + break; + + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + +} + +module.exports = PaymentByAccountPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressManualPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressManualPage.js new file mode 100644 index 000000000..6938ca270 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressManualPage.js @@ -0,0 +1,46 @@ + + +class RegisteredAddressManualPage { + + constructor() { + this.container = element(by.xpath(`//app-registered-address//h1[contains(text(),'Is this a UK address?')]`)) + + this.ukAddressradioInput = $(`input[name='ukAddress']`) + + this.fieldMapping = { + "Is this a UK address?": $('xuilib-write-address-field'), + "Enter address details": $('xuilib-write-address-inputs') + } + } + + async inputValue(field, value) { + switch (field) { + case "Is this a UK address?": + const ele = element(by.xpath(`//xuilib-write-address-field//label[contains(text(),'${value.trim()}')]/../input`)) + await ele.click(); + break; + case "Building and Street": + case "Address line 2": + case "Address line 3": + case "Town or City": + case "County/ State/ Province": + case "Country": + case "County": + case "Postcode": + await this.enterAddressInput(field, value) + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + async enterAddressInput(field, value){ + const ele = element(by.xpath(`//xuilib-write-address-inputs//label[contains(text(),'${field}')]/../input`)) + await ele.sendKeys(value) + } + + +} + +module.exports = RegisteredAddressManualPage + diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressPage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressPage.js new file mode 100644 index 000000000..93a11adb6 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/registeredAddressPage.js @@ -0,0 +1,44 @@ + + +class RegisteredAddressPage { + + constructor() { + this.container = $('app-registered-address') + + this.addressLookupInput = $('#addressLookup #postcodeInput'); + this.findAddressBtn = element(by.xpath(`//button[contains(text(),'Find address')]`)) + + this.addressListSelect = $('#selectAddress #addressList') + this.cantEnterAddressLink = element(by.xpath(`//a[contains(text(),"I can't enter a UK postcode")]`)) + + this.fieldMapping = { + 'Provide address details': $('#addressLookup'), + 'I can\'t enter a UK postcode': this.cantEnterAddressLink + } + } + + async inputValue(field, value) { + switch (field) { + case 'Provide address details': + const addressInput = value.split(',') + await this.selectAddress(addressInput[0].trim(), addressInput[1].trim()) + break; + case "I can't enter a UK postcode": + await this.cantEnterAddressLink.click() + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + async selectAddress(postcode, firstLineOdADdr){ + await this.addressLookupInput.sendKeys(postcode) + await this.findAddressBtn.click(); + + await this.addressListSelect.select(firstLineOdADdr); + } + + +} + +module.exports = RegisteredAddressPage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/regulatoryOrganisationTypePage.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/regulatoryOrganisationTypePage.js new file mode 100644 index 000000000..4d1417f3b --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/regulatoryOrganisationTypePage.js @@ -0,0 +1,41 @@ + +const browserWaits = require('../../../support/customWaits') +class RegulatoryOrganisationTypePage { + + constructor() { + this.container = $('app-regulatory-organisation-type') + + + + + this.fieldMapping = { + 'Select the type of regulatory organisation': $('select[name="regulatorType"]'), + 'Enter the name of the professional body or regulator': $('input[name="regulatorName"]'), + 'Enter your organisation\'s registration number': $('input[name="organisationRegistrationNumber"]') + } + } + + async inputValue(field, value) { + switch (field) { + case 'Select the type of regulatory organisation': + await this.waitForREgulatorTypeOption(value.trim()) + await this.fieldMapping[field].select(value.trim()) + break; + case "Enter the name of the professional body or regulator": + case "Enter your organisation's registration number": + await this.fieldMapping[field].sendKeys(value.trim()) + break; + default: + throw new Error(`${field} not configured in test pageObject`) + } + } + + async waitForREgulatorTypeOption(option){ + const ele = element(by.xpath(`//select[@name='regulatorType']//option[contains(text(),'${option}')]`)) + await browserWaits.waitForElement(ele) + } + + +} + +module.exports = RegulatoryOrganisationTypePage diff --git a/test_codecept/e2e/features/pageObjects/registerOtherOrg/workflow.js b/test_codecept/e2e/features/pageObjects/registerOtherOrg/workflow.js new file mode 100644 index 000000000..e4c02b4a4 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/registerOtherOrg/workflow.js @@ -0,0 +1,56 @@ + + +const BeforeYouStartPage = require('./beforeYouStartPage') +const OrganisationTypePage = require('./organisationTypePage') +const CompanyHouseDetailsPage = require('./companyHouseDetailsPage') +const RegisteredAddressPage = require('./registeredAddressPage') +const RegisteredAddressManualPage = require('./registeredAddressManualPage') +const DocumentExchangeReferencePage = require('./documentExchangeReferencePage') +const DocumentExchangeReferenceDetailsPage = require('./documentExchangeReferenceDetailsPage') +const RegulatoryOrganisationTypePage = require('./regulatoryOrganisationTypePage') +const OrganisationServicesAccessPage = require('./organisationServicesAccessPage') +const PaymentByAccountPage = require('./paymentByAccountPage') +const PaymentByAccountDetailsPage = require('./paymentByAccountDetailsPage') +const ContactDetailsPage = require('./contactDetailsPage') +const IndividualRegisteredWithRegulatorPage = require('./individualRegisteredWithRegulatorPage') +const IndividualRegisteredWithRegulatorDetailsPage = require('./individualRegisteredWithRegulatorDetailsPage') +const CheckYourAnswersPage = require('./checkYourAnswersPage') + +class RegisterOtherOrgWorkflow{ + constructor(){ + this.startBtn = element(by.xpath(`//button[contains(@class,'govuk-buton--primary')][contains(text(),'Start')]`)); + this.continueBtn = element(by.xpath(`//div[contains(@class,'govuk-button-group')]//button[contains(text(),'Continue')]`)); + this.backLink = $('a.govuk-back-link') + + this.errorSummaryContainer = $('#errorSummary') + + this.pages = { + "Apply for an organisation to manage civil, family and tribunal cases": new BeforeYouStartPage(), + "What type of organisation are you registering?": new OrganisationTypePage(), + "What is your company name and Companies House number?": new CompanyHouseDetailsPage(), + "What is the registered address of your organisation?": new RegisteredAddressPage(), + "Is this a UK address?": new RegisteredAddressManualPage(), + "Do you have a document exchange reference for your main office?": new DocumentExchangeReferencePage(), + "What's the DX reference for this office?": new DocumentExchangeReferenceDetailsPage(), + "Who is your organisation registered with?": new RegulatoryOrganisationTypePage(), + "Which services will your organisation need to access?": new OrganisationServicesAccessPage(), + "Does your organisation have a payment by account number?" : new PaymentByAccountPage(), + "What PBA numbers does your organisation use?": new PaymentByAccountDetailsPage(), + "Provide your contact details" : new ContactDetailsPage(), + "Are you (as an individual) registered with a regulator?": new IndividualRegisteredWithRegulatorPage(), + "What regulator are you (as an individual) registered with?": new IndividualRegisteredWithRegulatorDetailsPage(), + "Check your answers before you register" : new CheckYourAnswersPage() + + } + } + + async validateErrorSummaryMessageDisplayed(message){ + const ele = element(by.xpath(`//div[contains(@class,'govuk-error-summary__body')]//a[contains(text(),'${message}')]`)) + expect(await ele.isDisplayed(), `Error message not displayed: ${message}`).to.be.true + } + + +} + +module.exports = new RegisterOtherOrgWorkflow(); + diff --git a/test_codecept/e2e/features/pageObjects/shareCaseCheckYourAnswersPage.js b/test_codecept/e2e/features/pageObjects/shareCaseCheckYourAnswersPage.js new file mode 100644 index 000000000..5a3b1acc4 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/shareCaseCheckYourAnswersPage.js @@ -0,0 +1,57 @@ + + +class ShareCaseCheckYourAnswersPage{ + constructor(){ + this.container = $('app-exui-case-share-confirm') + this.header = $('app-exui-case-share-confirm h1') + + this.confirmButton = element(by.xpath(`//button[contains(text(),'Confirm')]`)) + + this.submissionsConfirmationPageContainer = $('app-exui-case-share-complete'); + this.submissionConfirmMessage = $('app-exui-case-share-complete h1') + } + + async clickConfirm(){ + await this.confirmButton.click() + } + + getCaseToSharePageObject(caseId){ + return new CaseToShare(caseId) + + } +} + + +class CaseToShare { + constructor(caseId) { + this.caseId = caseId; + this.caseContainer = element(by.xpath(`//xuilib-selected-case-confirm//div[contains(@id,'${this.caseId}')]`)) + this.caseHeader = element(by.xpath(`//xuilib-selected-case-confirm//h2[contains(text(),'${this.caseId}')]`)) + + } + + async isCaseDisplayed() { + return await this.caseContainer.isDisplayed(); + } + + + + async isUserDisplayed(userFullname) { + const user = element(by.xpath(`//xuilib-selected-case-confirm//h2[contains(text(),'${this.caseId}')]/..//td[contains(text(),'${userFullname}')]`)) + return await user.isDisplayed(); + } + + isUserEmailDisplayed(userFullname, email) { + const emailEle = element(by.xpath(`//xuilib-selected-case-confirm//h2[contains(text(),'${this.caseId}')]/..//td[contains(text(),'${userFullname}')]/../td[contains(text(),'${email}')]`)) + return emailEle.isDisplayed() + } + + + async isActionsDisplayed(userFullname, status) { + const email = element(by.xpath(`//xuilib-selected-case-confirm//h2[contains(text(),'${this.caseId}')]/..//td[contains(text(),'${userFullname}')]/../td/span[contains(@class,'hmcts-badge')]`)) + const statusText = await email.getText() + return statusText.toLowerCase().includes(status.toLowerCase()) + } +} + +module.exports = new ShareCaseCheckYourAnswersPage() diff --git a/test_codecept/e2e/features/pageObjects/shareCasePage.js b/test_codecept/e2e/features/pageObjects/shareCasePage.js new file mode 100644 index 000000000..2afa56dfe --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/shareCasePage.js @@ -0,0 +1,77 @@ +const browserWaits = require('../../support/customWaits') + +class ShareCasesPage{ + constructor(){ + this.container = element(by.xpath(`//xuilib-share-case//h1//span[contains(text(),'Share a case')]`)) + + this.emailAddressInput = element(by.xpath(`//label[contains(text(),'Enter email address')]/..//xuilib-user-select//input`)) + this.addEmailAddressButton = element(by.xpath(`//label[contains(text(),'Enter email address')]/..//button[contains(text(),'Add')]`)) + + this.continueButton = element(by.xpath(`//button[contains(text(),'Continue')]`)) + this.cabcelButton = element(by.xpath(`//button[contains(text(),'Cancel')]`)) + + } + + async selectUserToAdd(user){ + const ele = element(by.xpath(`//mat-option//span[contains(text(),'${user}')]`)) + await browserWaits.waitForElement(ele); + await ele.click(); + } + + getCaseToSharePageObject(caseId){ + return new CaseToShare(caseId) + } +} + +class CaseToShare{ + constructor(caseId){ + this.caseId = caseId; + this.caseContainer = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]`)) + this.caseHeader = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//h3`)) + this.deselectCaseButton = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//button[contains(text(),'Deselect case')]`)) + + this.accordianSectionContent = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//div[contains(@class,'govuk-accordion__section-content')]`)) + this.accordianSectionButton = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//button[contains(@class,'govuk-accordion__section-button')]/span[contains(@class,'govuk-accordion__icon')]`)) + + + } + + async isCaseDisplayed(){ + return await this.caseContainer.isDisplayed(); + } + + + + async isUserDisplayed(userFullname){ + const isContentDisplayed = await this.accordianSectionContent.isDisplayed(); + if (!isContentDisplayed) { + await this.accordianSectionButton.click(); + } + const user = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//td[contains(@id,'user-full-name')][contains(text(),'${userFullname}')]`)) + return await user.isDisplayed(); + } + + getActionLinkElement(userFullname, action){ + const ele = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//td[contains(@id,'user-full-name')][contains(text(),'${userFullname}')]/..//a[contains(text(),'${action}')]`)) + return ele + } + + async isActionDisplayed(userFullname, action){ + const ele = this.getActionLinkElement(userFullname, action); + return await ele.isDisplayed(); + } + + async clickActionLink(userFullname, action){ + const ele = this.getActionLinkElement(userFullname, action); + await ele.click(); + } + + async isStatusDisplayed(userFullname, status){ + const ele = element(by.xpath(`//xuilib-selected-case//div[contains(@id,'${this.caseId}')]//td[contains(@id,'user-full-name')][contains(text(),'${userFullname}')]/..//span[contains(@class,'hmcts-badge')]`)) + const statusText = await ele.getText() + return statusText.toLowerCase().includes(status.toLowerCase(0)) + } +} + + +module.exports = new ShareCasesPage(); \ No newline at end of file diff --git a/test_codecept/e2e/features/pageObjects/unassignedAssignedCasePage.js b/test_codecept/e2e/features/pageObjects/unassignedAssignedCasePage.js new file mode 100644 index 000000000..ab1537844 --- /dev/null +++ b/test_codecept/e2e/features/pageObjects/unassignedAssignedCasePage.js @@ -0,0 +1,122 @@ +const BrowserWaits = require('../../support/customWaits'); + +class UnassignedAssignedCasesPage{ + constructor(){ + this.headerTitle = $('h1.govuk-heading-xl'); + this.filterContainer = $('app-caa-filter') + + this.searchUnassignedCaseInput = element(by.xpath(`//h2[contains(text(),'Search for an unassigned case')]/../div/input`)) + this.filterApplyButton = element(by.xpath(`//button[contains(text(),'Apply filter')]`)) + this.filterResetButton = element(by.xpath(`//button[contains(text(),'Reset')]`)) + + this.shareCaseButton = element(by.xpath(`//button[contains(text(),'Share case')]`)) + + this.manageCaseSharingButton = element(by.xpath(`//button[contains(text(),'Manage case sharing')]`)) + + + this.errorSummary = $('.govuk-error-summary__body') + + } + + async isFilterContainerDisplayed(){ + return this.filterContainer.isDisplayed() + } + + getFilterButtonElement(action){ + return element(by.xpath(`//app-caa-cases-component//button[contains(text(),'${action}')]`)) + } + + getCaseTypeTabElement(tab){ + return element(by.xpath(`//div[contains(@class,'mat-tab-label-content')][contains(text(),'${tab}')]`)) + } + + isCaseTypeTabContainerDisplayed(tab){ + const ele = element(by.xpath(`//mat-tab-body//strong[contains(text(),'${tab}')]`)) + return ele.isDisplayed(); + } + + async isFilterButtonDisplayed(action){ + return await this.getFilterButtonElement(action).isDisplayed() + } + + async clickFilterButton(action){ + await this.getFilterButtonElement(action).click() + } + + async enterSearchCaseInput(caseId){ + await this.searchUnassignedCaseInput.sendKeys(caseId) + } + + async clickApplyFilterButton(){ + await this.filterApplyButton.click() + } + + async clickResetFilterButton() { + await this.filterResetButton.click() + } + + async isErrorSummaryDisplayed() { + return this.errorSummary.isDisplayed(); + } + + async getErrorSummary(){ + return this.errorSummary.getText(); + } + + async waitForPageToLoad(pageheaderText){ + await BrowserWaits.waitForElement(this.headerTitle, undefined, `${pageheaderText} Page header not displayed`); + await BrowserWaits.waitForCondition(async () => { + return (await this.headerTitle.getText()).includes(pageheaderText); + }); + } + + + async amOnPage(){ + await this.waitForPageToLoad(); + return true; + } + + + async validateCaseListColumnDisplaysValue(field, value){ + const column = element(by.xpath(`//th[contains(@class,'govuk-table__header')][contains(text(),'${field}')]`)) + const colvalue = element(by.xpath(`//td[contains(@class,'govuk-table__cell')][contains(text(),'${value}')]`)) + expect(await column.isDisplayed()).to.be.true + expect(await colvalue.isDisplayed()).to.be.true + + } + + async selectCase(caseid){ + const ele = element(by.xpath(`//th//input[contains(@id,'${caseid}')]`)) + await ele.click() + } + + async isShareCaseButttonEnabled(){ + const attr = await this.shareCaseButton.getAttribute('disabled') + return attr === null + } + + async isManageCaseSharingutttonEnabled() { + const attr = await this.manageCaseSharingButton.getAttribute('disabled') + return attr === null + } + + async clickShareCaseButton(){ + await this.shareCaseButton.click() + } + + async clickManageCaseSharingButton() { + await this.manageCaseSharingButton.click() + } + + +} + +class AssignedCasesFilter{ + constructor(){ + + } + + +} + +module.exports = new UnassignedAssignedCasesPage(); diff --git a/test_codecept/e2e/features/pageObjects/unassignedCasePage.js b/test_codecept/e2e/features/pageObjects/unassignedCasePage.js deleted file mode 100644 index d59f1f4ba..000000000 --- a/test_codecept/e2e/features/pageObjects/unassignedCasePage.js +++ /dev/null @@ -1,22 +0,0 @@ -const BrowserWaits = require('../../support/customWaits'); -const { isConstructorDeclaration } = require('typescript'); - -class UnassignedCasesPage{ - constructor(){ - this.headerTitle = $('h1.govuk-heading-xl'); - } - - async waitForPageToLoad(){ - await BrowserWaits.waitForElement(this.headerTitle, undefined, 'Unassigned Cses Page header not displayed'); - await BrowserWaits.waitForCondition(async () => { - return (await this.headerTitle.getText()).includes('Unassigned cases'); - }); - } - - async amOnPage(){ - await this.waitForPageToLoad(); - return true; - } -} - -module.exports = new UnassignedCasesPage(); diff --git a/test_codecept/e2e/features/step_definitions/assignedCase.steps.js b/test_codecept/e2e/features/step_definitions/assignedCase.steps.js new file mode 100644 index 000000000..c1cc25bd4 --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/assignedCase.steps.js @@ -0,0 +1,70 @@ + +const Header = require('../pageObjects/headerPage'); +const unassignedAssignedCasesPage = require('../pageObjects/unassignedAssignedCasePage'); +const reportLogger = require('../../../codeceptCommon/reportLogger'); +const header = new Header(); + +When('I click on Assigned cases tab', async function () { + await header.clickAssignedCases(); +}); + + +Then('I should be on display assigned cases page', async function () { + await unassignedAssignedCasesPage.waitForPageToLoad('Assigned Cases'); +}); + + +Then('In assigned cases page, filter button {string} is displayed', async function (filterShowHideButton) { + expect(await unassignedAssignedCasesPage.isFilterButtonDisplayed(filterShowHideButton)).to.be.true; +}); + +Then('In assigned cases page, filter button {string} is not displayed', async function (filterShowHideButton) { + expect(await unassignedAssignedCasesPage.isFilterButtonDisplayed(filterShowHideButton)).to.be.false; +}); + +When('In assigned cases page, I click filter button {string}', async function (filterButton) { + await unassignedAssignedCasesPage.clickFilterButton(filterButton); +}); + + + +Then('In assigned cases page, I case type tab {string} displayed', async function (caseTypeTab) { + expect(await unassignedAssignedCasesPage.getCaseTypeTabElement(caseTypeTab).isDisplayed()).to.be.true; +}); + +When('In assigned cases page, I click case type tab {string}', async function (caseTypeTab) { + await unassignedAssignedCasesPage.getCaseTypeTabElement(caseTypeTab).click(); +}); + +Then('In assigned cases page, I see case type cases container {string}', async function (caseTypeTab) { + expect(await unassignedAssignedCasesPage.isCaseTypeTabContainerDisplayed(caseTypeTab)).to.be.true; +}); + +Then('In assigned cases page, I see case list displays value', async function (datatable) { + + const datatablehashes = datatable.parse().hashes(); + + for (let row of datatablehashes) { + reportLogger.AddMessage(`Validating fields display: ${row.field} => ${row.value}`); + + await unassignedAssignedCasesPage.validateCaseListColumnDisplaysValue(row.field, row.value); + } +}); + +Then('In assigned cases page, I see Manage case sharing button disabled', async function (caseid) { + expect(await unassignedAssignedCasesPage.isManageCaseSharingutttonEnabled()).to.be.false; +}); + +Then('In assigned cases page, I see Manage case sharing button enabled', async function (caseid) { + expect(await unassignedAssignedCasesPage.isManageCaseSharingutttonEnabled()).to.be.true; +}); + +When('In assigned cases page, I click Manage case sharing button', async function (caseid) { + await unassignedAssignedCasesPage.manageCaseSharingButton.click(); +}); + +When('In assigned cases page, I select case with id {string}', async function (caseid) { + await unassignedAssignedCasesPage.selectCase(caseid); +}); + + diff --git a/test_codecept/e2e/features/step_definitions/common.steps.js b/test_codecept/e2e/features/step_definitions/common.steps.js new file mode 100644 index 000000000..6e49df9a6 --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/common.steps.js @@ -0,0 +1,9 @@ + + +Then('I sleep for sec {int}' , async function(waitFor){ + await new Promise((resolve,reject) => { + setTimeout(() => { + resolve(true) + }, waitFor*1000 ) + }) +}) \ No newline at end of file diff --git a/test_codecept/e2e/features/step_definitions/createOrganisation.steps.js b/test_codecept/e2e/features/step_definitions/createOrganisation.steps.js index efa4caea0..9e693c418 100644 --- a/test_codecept/e2e/features/step_definitions/createOrganisation.steps.js +++ b/test_codecept/e2e/features/step_definitions/createOrganisation.steps.js @@ -30,10 +30,9 @@ async function waitForElement(el) { Then('I am on Register organisation start page', async function () { await createOrganisationObject.waitForStartRegisterPage(); await expect(createOrganisationObject.start_button.isDisplayed(), 'Create Organisation START button not present').to.eventually.be.true; - await expect(createOrganisationObject.start_button.getText(), 'Start button text not mathing with expected') + await expect(await createOrganisationObject.start_button.getText(), 'Start button text not mathing with expected') .to - .eventually - .equal('Start'); + .includes('Start'); }); Then(/^I land on register organisation page and continue$/, async function () { @@ -46,7 +45,7 @@ async function waitForElement(el) { await expect(createOrganisationObject.start_button.getText()) .to .eventually - .equal('Start'); + .include('Start'); await createOrganisationObject.start_button.click(); await createOrganisationObject.waitForPage("What's the name of your organisation?"); @@ -140,10 +139,9 @@ async function waitForElement(el) { // await waitForElement('govuk-heading-l'); await expect(createOrganisationObject.submit_button.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.submit_button.getText()) + await expect(await createOrganisationObject.submit_button.getText()) .to - .eventually - .equal('Confirm and submit details'); + .includes('Confirm and submit details'); await createOrganisationObject.submit_button.click(); }); @@ -151,10 +149,9 @@ async function waitForElement(el) { // browser.sleep(MID_DELAY); await createOrganisationObject.waitForSubmission(); await expect(createOrganisationObject.org_success_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.org_success_heading.getText()) + await expect(await createOrganisationObject.org_success_heading.getText()) .to - .eventually - .equal('Registration details submitted'); + .includes('Registration details submitted'); }); When(/^I am not entered Organization name$/, async function () { @@ -165,10 +162,9 @@ async function waitForElement(el) { Then(/^I should be display organization error$/, async function () { await expect(createOrganisationObject.org_failure_error_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.org_failure_error_heading.getText()) + await expect(await createOrganisationObject.org_failure_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When(/^I am not entered the Office Address details$/, async function () { @@ -180,10 +176,9 @@ async function waitForElement(el) { }); Then(/^I should be display Office Address error$/, async function () { await expect(createOrganisationObject.off_address_error_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.off_address_error_heading.getText()) + await expect(await createOrganisationObject.off_address_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When(/^I am not entered SRA number$/, async function () { @@ -196,10 +191,9 @@ async function waitForElement(el) { Then(/^I should be display SRA error$/, async function () { await createOrganisationObject.waitForPage('Enter your organisation SRA ID'); - await expect(createOrganisationObject.sra_error_heading.getText()) + await expect(await createOrganisationObject.sra_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When(/^I am not entered the email address$/, async function () { @@ -211,10 +205,9 @@ async function waitForElement(el) { Then(/^I should be display email error$/, async function () { await expect(createOrganisationObject.email_error_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.email_error_heading.getText()) + await expect(await createOrganisationObject.email_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When(/^I Enter the invalid PBA1 and PBA2 details$/, async function () { @@ -227,10 +220,9 @@ async function waitForElement(el) { Then(/^I should be display PBA error$/, async function () { await expect(createOrganisationObject.pba_error_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.pba_error_heading.getText()) + await expect(await createOrganisationObject.pba_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When(/^I am not entered the firstName and lastName$/, async function () { @@ -243,10 +235,9 @@ async function waitForElement(el) { Then(/^I should be display firstName and lastName error$/, async function () { await expect(createOrganisationObject.name_error_heading.isDisplayed()).to.eventually.be.true; - await expect(createOrganisationObject.name_error_heading.getText()) + await expect(await createOrganisationObject.name_error_heading.getText()) .to - .eventually - .equal('There is a problem'); + .includes('There is a problem'); }); When('I am on page {string} in registration step', async function (page) { @@ -265,10 +256,9 @@ async function waitForElement(el) { be.true; }); - Then('I see manage org link under already registered account header', function () { - expect(createOrganisationObject.isManageOrgLinkPresent()).to - .eventually. - be.true; + Then('I see manage org link under already registered account header', async function () { + expect(await createOrganisationObject.isManageOrgLinkPresent()).to + .be.true; }); Then('I click and validate MC link opens in new tab', async function () { diff --git a/test_codecept/e2e/features/step_definitions/inviteUser.steps.js b/test_codecept/e2e/features/step_definitions/inviteUser.steps.js index 4c805f5f9..0b9c8adb0 100644 --- a/test_codecept/e2e/features/step_definitions/inviteUser.steps.js +++ b/test_codecept/e2e/features/step_definitions/inviteUser.steps.js @@ -60,7 +60,7 @@ const { Error } = require('globalthis/implementation'); await browserWaits.waitForElement(inviteUserPage.userInvitaionConfirmation) await browserWaits.retryWithActionCallback(async () => { - expect(await inviteUserPage.amOnUserConfirmationPage()).to.be.true; + expect(await inviteUserPage.userInvitaionConfirmation.getText()).to.include('You\'ve invited'); }) }); diff --git a/test_codecept/e2e/features/step_definitions/registerOrgWorkflow.steps.js b/test_codecept/e2e/features/step_definitions/registerOrgWorkflow.steps.js new file mode 100644 index 000000000..cad81f2e7 --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/registerOrgWorkflow.steps.js @@ -0,0 +1,66 @@ + +const browser = require('../../../codeceptCommon/browser'); +const registerOrgWorkflow = require('../pageObjects/registerOtherOrg/workflow') + +function getPageObject(page) { + const pageObj = registerOrgWorkflow.pages[page]; + if (pageObj === null || pageObj === undefined) { + throw Error(`page object for page not configured or miss spelled: ${page} ${Object.keys(registerOrgWorkflow.pages)}`) + } + return pageObj; +} + +When('I click continue in register organisation workflow', async function () { + await registerOrgWorkflow.continueBtn.click(); +}) + +When('I click back in register organisation workflow', async function () { + await registerOrgWorkflow.backLink.click(); +}) + +Then('I am on register organisation page {string}', async function (page) { + expect(await getPageObject(page).isDisplayed(), `${page} not displayed`).to.be.true +}) + +Then('In register organisation page {string}, I validate fields displayed', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + for (let row of datatablehashes) { + expect(await pageObj.fieldMapping[row.name].isDisplayed(), `${row.name} not displayed`).to.be.true + + } + +}) + +Then('In register organisation page {string}, I validate fields not displayed', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + for (let row of datatablehashes) { + expect(await pageObj.fieldMapping[row.name].isDisplayed(), `${row.name} is displayed`).to.be.false + } +}) + + +When('In register organisation page {string}, I input values', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + + for (const row of datatablehashes) { + await pageObj.inputValue(row.field, row.value); + + } +}) + +When('In register organisation workflow, I navigate to route {string}', async function (route) { + await browser.get(`${process.env.TEST_URL}/register-org-new/${route}`) +}) + +Then('In register organisation workflow, I validate error messages displayed', async function (datatable) { + + const datatableHash = datatable.parse().hashes(); + for (let row of datatableHash) { + await registerOrgWorkflow.validateErrorSummaryMessageDisplayed(row.message) + } +}) + + diff --git a/test_codecept/e2e/features/step_definitions/registerOtherOrg.steps.js b/test_codecept/e2e/features/step_definitions/registerOtherOrg.steps.js new file mode 100644 index 000000000..add2a7b0f --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/registerOtherOrg.steps.js @@ -0,0 +1,150 @@ + +const workflow = require('../pageObjects/registerOtherOrg/workflow') + +const reportLogger = require('../../../codeceptCommon/reportLogger') +const browserWaits = require('../../support/customWaits') +const registerOrgWorkflow = require('../pageObjects/registerOtherOrg/workflow') + +function getPageObject(page) { + const pageObj = workflow.pages[page]; + if (pageObj === null || pageObj === undefined) { + throw Error(`page object for page not configured or miss spelled: ${page} ${Object.keys(workflow.pages)}`) + } + return pageObj; +} + +Given('I navigate to register other org start page', async function(){ + await browser.get(`${process.env.TEST_URL}/register-org-new`) + const ele = workflow.pages["Apply for an organisation to manage civil, family and tribunal cases"].container + await browserWaits.waitForElement(ele) + +}) + +When('I click start button in before you start page', async function(){ + await workflow.startBtn.click(); +}) + +When('I click continue in register other org workflow', async function () { + await workflow.continueBtn.click(); +}) + +Then('I am on register other org page {string}', async function (page) { + const pageObj = getPageObject(page); + await browserWaits.waitForElement(pageObj.container) + expect(await pageObj.container.isDisplayed(), `${page} not displayed`).to.be.true +}) + +Then('In register other org page {string}, I validate fields displayed', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + + reportLogger.AddMessage(`Validating fields display:`) + for (let row of datatablehashes) { + if (!Object.keys(pageObj.fieldMapping).includes(row.field)){ + throw new Error(`${row.field} not configured for page ${page}`) + } + expect(await pageObj.fieldMapping[row.field].isDisplayed(), `${row.field} not displayed`).to.be.true + reportLogger.AddMessage(`${row.name} is displayed`) + } + +}) + +Then('In register other org page {string}, I validate fields not displayed', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + for (let row of datatablehashes) { + expect(await pageObj.fieldMapping[row.field].isDisplayed(), `${row.field} is displayed`).to.be.false + reportLogger.AddMessage(`${row.field} is not displayed`) + } +}) + + +When('In register other org page {string}, I input values', async function (page, datatable) { + const datatablehashes = datatable.parse().hashes(); + const pageObj = getPageObject(page); + + for (const row of datatablehashes) { + await pageObj.inputValue(row.field, row.value); + reportLogger.AddMessage(`Done: ${row.field} input ${row.value}`) + } +}) + + +When('In register other org work flow, I click submit request', async function (page, datatable) { + await workflow.clickSubmitRequest() +}) + + + +Then('In register other org workflow, I validate check your answers displayed', async function (datatable) { + const datatableHash = datatable.parse().hashes(); + for (let row of datatableHash) { + reportLogger.AddMessage(`Validating ${row.field}=${row.value}`) + await workflow.pages["Check your answers before you register"].validateSummaryFieldWithValueDisplayed(row.field, row.value) + } +}) + +Then('In register other org workflow, I validate check your answers not displays fields', async function (datatable) { + const datatableHash = datatable.parse().hashes(); + for (let row of datatableHash) { + reportLogger.AddMessage(`Validating ${row.field}`) + await workflow.pages["Check your answers before you register"].validateSummaryFieldNotDisplayed(row.field) + } +}) + +When('In register other org work flow, I click back link', async function () { + await workflow.backLink.click(); +}) + + +When('In register other org check your answers page, I click change link for field {string}', async function (field) { + await workflow.pages['Check your answers before you register'].clickChangeLinkForField(field) +}) + + +Then('In register other org workflow, I validate change links' , async function(datatable){ + const datatableHash = datatable.parse().hashes(); + + const sessionStorage = await browser.getSessionStorage('Registration-Data') + reportLogger.AddMessage(sessionStorage) + for (let row of datatableHash) { + reportLogger.AddMessage(`Validating chnage link ${row.field} to page ${row.screen}`) + await workflow.pages['Check your answers before you register'].clickChangeLinkForField(row.field) + + const pageObj = getPageObject(row.screen); + await browserWaits.waitForElement(pageObj.container) + expect(await pageObj.container.isDisplayed(), `${row.screen} not displayed`).to.be.true + + await browser.get(`${process.env.TEST_URL}/register-org-new/check-your-answers`) + + const cyaPageObject = getPageObject('Check your answers before you register'); + await browserWaits.waitForElement(cyaPageObject.container) + } + +}) + +Then('In register other org workflow, I validate continue pages', async function (datatable){ + const datatableHash = datatable.parse().hashes(); + + const sessionStorage = await browser.getSessionStorage('Registration-Data') + reportLogger.AddMessage(sessionStorage) + + for (let row of datatableHash) { + reportLogger.AddMessage(`Validating continue to ${row.page} `) + await registerOrgWorkflow.continueBtn.click(); + + const pageObj = getPageObject(row.page); + await browserWaits.waitForElement(pageObj.container) + expect(await pageObj.container.isDisplayed(), `${row.page} not displayed`).to.be.true + + if (row.page === 'What is the registered address of your organisation?'){ + await pageObj.inputValue('Provide address details','SW1V 3BZ,Flat 1') + } + + } + +}) + + + + diff --git a/test_codecept/e2e/features/step_definitions/shareCase.steps.js b/test_codecept/e2e/features/step_definitions/shareCase.steps.js new file mode 100644 index 000000000..b4c55f479 --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/shareCase.steps.js @@ -0,0 +1,101 @@ + + +const shareACasePage = require('../pageObjects/shareCasePage') +const cyaPage = require('../pageObjects/shareCaseCheckYourAnswersPage') +Then('I see share case page', async function(){ + expect(await shareACasePage.container.isDisplayed()).to.be.true +}) + +Then('In share case page, I see email address input field', async function () { + expect(await shareACasePage.emailAddressInput.isDisplayed()).to.be.true +}) + +When('In share case page, I input email address {string}', async function(email){ + await shareACasePage.emailAddressInput.sendKeys(email) +}) + +When('In share case page, I select user {string} from results', async function (email) { + await shareACasePage.selectUserToAdd(email) +}) + +When('In share case page, I click Add user button', async function () { + await shareACasePage.addEmailAddressButton.click() +}) + +Then('In share case page, I see case with id {string} listed', async function(caseId){ + const caseObj = shareACasePage.getCaseToSharePageObject(caseId) + expect(await caseObj.isCaseDisplayed()).to.be.true +}) + +Then('In share case page, I validate users for case id {string}', async function (caseId, datatable) { + const datatablehashes = datatable.parse().hashes(); + + const caseObj = shareACasePage.getCaseToSharePageObject(caseId) + for (const row of datatablehashes){ + const name = row.Name; + const action = row.Action + const status = row.Status + expect(await caseObj.isUserDisplayed(name), `For case ${this.caseId}, user ${name} not displayed`).to.be.true + expect(await caseObj.isActionDisplayed(name, action), `For case ${this.caseId}, action ${action} not displayed`).to.be.true + expect(await caseObj.isStatusDisplayed(name, status), `For case ${this.caseId}, status ${status} not displayed`).to.be.true + } +}) + +Then('In share case page, I validate user {string} not displayed for case id {string}', async function (user, caseId) { + const caseObj = shareACasePage.getCaseToSharePageObject(caseId) + expect(await caseObj.isUserDisplayed(user)).to.be.false +}) + +When('In share case page, I click cancel for user {string} in case {string}', async function (user, caseId) { + const caseObj = shareACasePage.getCaseToSharePageObject(caseId) + await caseObj.clickActionLink(user, 'Cancel') +}) + + +When('In share case page, I click continue', async function (user, caseId) { + await shareACasePage.continueButton.click() +}) + +Then('I see Share case check and confirm your selection page', async function(){ + expect(await cyaPage.container.isDisplayed()).to.be.true +}) + +Then('I see Share case check and confirm your selection page with header {string}', async function (headerText) { + expect(await cyaPage.header.getText()).to.includes(headerText) +}) + +Then('In share case CYA page, case {string} displays users', async function (caseId, datatable) { + const caseObj = cyaPage.getCaseToSharePageObject(caseId) + + const datatablehashes = datatable.parse().hashes(); + + for (const row of datatablehashes) { + const name = row.Name; + const email = row.Email; + const actions = row.Actions + expect(await caseObj.isUserDisplayed(name), `For case ${this.caseId}, user ${name} not displayed`).to.be.true + expect(await caseObj.isActionsDisplayed(name, actions), `For case ${this.caseId}, action ${actions} not displayed`).to.be.true + expect(await caseObj.isUserEmailDisplayed(name, email), `For case ${this.caseId}, status ${email} not displayed`).to.be.true + } + +}) + + +When('In Share case CYA page, I click Confirm button', async function (headerText) { + await cyaPage.confirmButton.click() +}) + +Then('In share case workflow, I see share case confirmation', async function(){ + expect(await cyaPage.submissionsConfirmationPageContainer.isDisplayed()).to.be.true +}) + +Then('In share case workflow, I see cinfirmation message {string}', async function (message) { + expect(await cyaPage.submissionConfirmMessage.getText()).to.includes(message) +}) + + + + +// When('In share a case page, I click case type tab {string}', async function (caseTypeTab) { +// await unassignedAssignedCasesPage.getCaseTypeTabElement(caseTypeTab).click(); +// }); \ No newline at end of file diff --git a/test_codecept/e2e/features/step_definitions/unassignedCases.steps.js b/test_codecept/e2e/features/step_definitions/unassignedCases.steps.js new file mode 100644 index 000000000..0182d5c26 --- /dev/null +++ b/test_codecept/e2e/features/step_definitions/unassignedCases.steps.js @@ -0,0 +1,89 @@ + +const Header = require('../pageObjects/headerPage'); +const unassignedAssignedCasesPage = require('../pageObjects/unassignedAssignedCasePage'); +const reportLogger = require('../../../codeceptCommon/reportLogger'); +const header = new Header(); + +When('I click on Unassigned cases tab', async function(){ + await header.clickUnassignedCases(); +}); + + +Then('I should be on display Unassigned cases page', async function () { + await unassignedAssignedCasesPage.waitForPageToLoad('Unassigned Cases'); +}); + +Then('In unassigned cases page, filter button {string} is displayed', async function (filterShowHideButton) { + expect(await unassignedAssignedCasesPage.isFilterButtonDisplayed(filterShowHideButton)).to.be.true; +}); + +Then('In unassigned cases page, filter button {string} is not displayed', async function (filterShowHideButton) { + expect(await unassignedAssignedCasesPage.isFilterButtonDisplayed(filterShowHideButton)).to.be.false; +}); + +When('In unassigned cases page, I click filter button {string}', async function(filterButton){ + await unassignedAssignedCasesPage.clickFilterButton(filterButton); +}); + +When('In unassigned cases page, I click apply filter button', async function () { + await unassignedAssignedCasesPage.clickApplyFilterButton(); +}); + +When('In unassigned cases page, I click reset filter button', async function () { + await unassignedAssignedCasesPage.clickResetFilterButton(); +}); + +Then('In unassigned cases page, I see error summary displayed', async function(){ + expect(await unassignedAssignedCasesPage.isErrorSummaryDisplayed()).to.be.true; +}); + +Then('In unassigned cases page, I see error message {string}', async function(errorMessage){ + expect(await unassignedAssignedCasesPage.getErrorSummary()).to.includes(errorMessage); +}); + +Then('In unassigned cases page, I case type tab {string} displayed', async function (caseTypeTab) { + expect(await unassignedAssignedCasesPage.getCaseTypeTabElement(caseTypeTab).isDisplayed()).to.be.true; +}); + +When('In unassigned cases page, I click case type tab {string}', async function (caseTypeTab) { + await unassignedAssignedCasesPage.getCaseTypeTabElement(caseTypeTab).click(); +}); + +Then('In unassigned cases page, I see case type cases container {string}', async function (caseTypeTab) { + expect(await unassignedAssignedCasesPage.isCaseTypeTabContainerDisplayed(caseTypeTab)).to.be.true; +}); + +Then('In unassigned cases page, I see case list displays value', async function (datatable) { + + const datatablehashes = datatable.parse().hashes(); + + for (let row of datatablehashes) { + reportLogger.AddMessage(`Validating fields display: ${row.field} => ${row.value}`); + + await unassignedAssignedCasesPage.validateCaseListColumnDisplaysValue(row.field, row.value); + } +}); + +Then('In unassigned cases page, I select case with id {string}', async function (caseid) { + await unassignedAssignedCasesPage.selectCase(caseid); +}); + +Then('In unassigned cases page, I see share case button disabled', async function (caseid) { + expect(await unassignedAssignedCasesPage.isShareCaseButttonEnabled()).to.be.false; +}); + +Then('In unassigned cases page, I see share case button enabled', async function (caseid) { + expect(await unassignedAssignedCasesPage.isShareCaseButttonEnabled()).to.be.true; +}); + + +When('In unassigned cases page, I select case with id {string}', async function (caseid) { + await unassignedAssignedCasesPage.selectCase(caseid); +}); + +When('In unassigned cases page, I click share case button', async function (caseid) { + await unassignedAssignedCasesPage.clickShareCaseButton(caseid); +}); + + + diff --git a/test_codecept/ngIntegration/tests/features/assignedCase.feature b/test_codecept/ngIntegration/tests/features/assignedCase.feature new file mode 100644 index 000000000..146dbee3d --- /dev/null +++ b/test_codecept/ngIntegration/tests/features/assignedCase.feature @@ -0,0 +1,106 @@ +@ng @fullFunctional +Feature: Assigned cases Tab + Background: Mock and browser setup + Given I init MockApp + + + Scenario: Filter button + + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + + Then I should be redirected to manage organisation dashboard page + When I click on Assigned cases tab + Then I should be on display assigned cases page + Then In assigned cases page, filter button "Show assigned cases filter" is displayed + Then In assigned cases page, filter button "Hide assigned cases filter" is not displayed + When In assigned cases page, I click filter button "Show assigned cases filter" + Then In assigned cases page, filter button "Hide assigned cases filter" is displayed + Then In assigned cases page, filter button "Show assigned cases filter" is not displayed + When In assigned cases page, I click filter button "Hide assigned cases filter" + Then In assigned cases page, filter button "Show assigned cases filter" is displayed + Then In assigned cases page, filter button "Hide assigned cases filter" is not displayed + + + Scenario: Case types and case list container + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Assigned cases tab + Then I should be on display assigned cases page + When In assigned cases page, I click case type tab "Asylum" + Then In assigned cases page, I see case type cases container "Asylum" + When In assigned cases page, I click case type tab "Immigration" + Then In assigned cases page, I see case type cases container "Immigration" + Then In assigned cases page, I see case list displays value + | field | value | + | Case Reference | 1234567812345671 | + | Case Number | 6042070/2023 | + | Claimant | Grayson Becker | + + + + Scenario: Share case button state + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Assigned cases tab + Then I should be on display assigned cases page + When In assigned cases page, I click case type tab "Asylum" + Then In assigned cases page, I see case type cases container "Asylum" + Then In assigned cases page, I see Manage case sharing button disabled + When In assigned cases page, I select case with id "1234567812345671" + Then In assigned cases page, I see Manage case sharing button enabled + + +@functional_debug @ignore + Scenario: Share case scenario 1 + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Assigned cases tab + Then I should be on display assigned cases page + When In assigned cases page, I click case type tab "Asylum" + Then In assigned cases page, I see case type cases container "Asylum" + Then In assigned cases page, I see Manage case sharing button disabled + When In assigned cases page, I select case with id "1234567812345671" + When In assigned cases page, I select case with id "1234567812345672" + + Then In assigned cases page, I see Manage case sharing button enabled + + When In assigned cases page, I click Manage case sharing button + Then I see share case page + Then In share case page, I see case with id "1234567812345671" listed + Then In share case page, I see case with id "1234567812345672" listed + + Then In share case page, I see email address input field + When In share case page, I input email address "pet" + When In share case page, I select user "Pet Solicitor 2" from results + When In share case page, I click Add user button + + Then In share case page, I validate users for case id "1234567812345671" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + Then In share case page, I validate users for case id "1234567812345672" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + When In share case page, I click cancel for user "Pet Solicitor 2" in case "1234567812345671" + Then In share case page, I validate user "Pet Solicitor 2" not displayed for case id "1234567812345671" + + Then In share case page, I validate users for case id "1234567812345672" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + When In share case page, I click continue + + Then I see Share case check and confirm your selection page + Then I see Share case check and confirm your selection page with header "Check and confirm your selection" + Then In share case CYA page, case "1234567812345672" displays users + | Name | Email | Actions | + | Pet Solicitor 2 | div-petsol-2@mailinator.com | TO BE ADDED | + When In Share case CYA page, I click Confirm button + Then In share case workflow, I see share case confirmation + Then In share case workflow, I see cinfirmation message "Your selected cases have been updated" + diff --git a/test_codecept/ngIntegration/tests/features/unassignedCases.feature b/test_codecept/ngIntegration/tests/features/unassignedCases.feature new file mode 100644 index 000000000..f505b2cdb --- /dev/null +++ b/test_codecept/ngIntegration/tests/features/unassignedCases.feature @@ -0,0 +1,172 @@ +@ng @fullFunctional +Feature: Unassigned cases Tab + Background: Mock and browser setup + Given I init MockApp + + + Scenario: Filter button + + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + Then In unassigned cases page, filter button "Show unassigned cases filter" is displayed + Then In unassigned cases page, filter button "Hide unassigned cases filter" is not displayed + When In unassigned cases page, I click filter button "Show unassigned cases filter" + Then In unassigned cases page, filter button "Hide unassigned cases filter" is displayed + Then In unassigned cases page, filter button "Show unassigned cases filter" is not displayed + When In unassigned cases page, I click filter button "Hide unassigned cases filter" + Then In unassigned cases page, filter button "Show unassigned cases filter" is displayed + Then In unassigned cases page, filter button "Hide unassigned cases filter" is not displayed + + + + Scenario: Filter container validation + + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + Then In unassigned cases page, filter button "Show unassigned cases filter" is displayed + Then In unassigned cases page, filter button "Hide unassigned cases filter" is not displayed + When In unassigned cases page, I click filter button "Show unassigned cases filter" + Then In unassigned cases page, filter button "Hide unassigned cases filter" is displayed + Then In unassigned cases page, filter button "Show unassigned cases filter" is not displayed + When In unassigned cases page, I click apply filter button + Then In unassigned cases page, I see error summary displayed + Then In unassigned cases page, I see error message "Enter a valid HMCTS case reference number" + + Scenario: Case types and case list container + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + When In unassigned cases page, I click case type tab "Asylum" + Then In unassigned cases page, I see case type cases container "Asylum" + When In unassigned cases page, I click case type tab "Immigration" + Then In unassigned cases page, I see case type cases container "Immigration" + Then In unassigned cases page, I see case list displays value + | field | value | + | Case Reference | 1234567812345671 | + | Case Number | 6042070/2023 | + | Claimant | Grayson Becker | + + + Scenario: Share case button state + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + When In unassigned cases page, I click case type tab "Asylum" + Then In unassigned cases page, I see case type cases container "Asylum" + Then In unassigned cases page, I see share case button disabled + When In unassigned cases page, I select case with id "1234567812345671" + Then In unassigned cases page, I see share case button enabled + + + Scenario: Share case scenario 1 + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + When In unassigned cases page, I click case type tab "Asylum" + Then In unassigned cases page, I see case type cases container "Asylum" + Then In unassigned cases page, I see share case button disabled + When In unassigned cases page, I select case with id "1234567812345671" + When In unassigned cases page, I select case with id "1234567812345672" + + Then In unassigned cases page, I see share case button enabled + + When In unassigned cases page, I click share case button + Then I see share case page + Then In share case page, I see case with id "1234567812345671" listed + Then In share case page, I see case with id "1234567812345672" listed + + Then In share case page, I see email address input field + When In share case page, I input email address "pet" + When In share case page, I select user "Pet Solicitor 2" from results + When In share case page, I click Add user button + + Then In share case page, I validate users for case id "1234567812345671" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel| TO BE ADDED | + + Then In share case page, I validate users for case id "1234567812345672" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + When In share case page, I click cancel for user "Pet Solicitor 2" in case "1234567812345671" + Then In share case page, I validate user "Pet Solicitor 2" not displayed for case id "1234567812345671" + + Then In share case page, I validate users for case id "1234567812345672" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + When In share case page, I click continue + + Then I see Share case check and confirm your selection page + Then I see Share case check and confirm your selection page with header "Check and confirm your selection" + Then In share case CYA page, case "1234567812345672" displays users + | Name | Email | Actions | + | Pet Solicitor 2 | div-petsol-2@mailinator.com | TO BE ADDED | + When In Share case CYA page, I click Confirm button + Then In share case workflow, I see share case confirmation + Then In share case workflow, I see cinfirmation message "Your selected cases have been updated" + + + Scenario: Share case scenario 2 + Given I set MOCK with user roles + | roles | pui-org-manager,task-supervisor,case-allocator | + Then I should be redirected to manage organisation dashboard page + When I click on Unassigned cases tab + Then I should be on display Unassigned cases page + When In unassigned cases page, I click case type tab "Asylum" + Then In unassigned cases page, I see case type cases container "Asylum" + Then In unassigned cases page, I see share case button disabled + When In unassigned cases page, I select case with id "1234567812345671" + When In unassigned cases page, I select case with id "1234567812345672" + + Then In unassigned cases page, I see share case button enabled + + When In unassigned cases page, I click share case button + + # Then I sleep for sec 300 + + Then I see share case page + Then In share case page, I see case with id "1234567812345671" listed + Then In share case page, I see case with id "1234567812345672" listed + + Then In share case page, I see email address input field + When In share case page, I input email address "pet" + When In share case page, I select user "Pet Solicitor 2" from results + When In share case page, I click Add user button + + Then In share case page, I validate users for case id "1234567812345671" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + Then In share case page, I validate users for case id "1234567812345672" + | Name | Action | Status | + | Pet Solicitor 2 | Cancel | TO BE ADDED | + + + When In share case page, I click continue + + Then I see Share case check and confirm your selection page + Then I see Share case check and confirm your selection page with header "Check and confirm your selection" + Then In share case CYA page, case "1234567812345671" displays users + | Name | Email | Actions | + | Pet Solicitor 2 | div-petsol-2@mailinator.com | TO BE ADDED | + Then In share case CYA page, case "1234567812345672" displays users + | Name | Email | Actions | + | Pet Solicitor 2 | div-petsol-2@mailinator.com | TO BE ADDED | + When In Share case CYA page, I click Confirm button + Then In share case workflow, I see share case confirmation + Then In share case workflow, I see cinfirmation message "Your selected cases have been updated" diff --git a/test_codecept/ngIntegration/tests/headerTabs.js b/test_codecept/ngIntegration/tests/headerTabs.js deleted file mode 100644 index 443b7276e..000000000 --- a/test_codecept/ngIntegration/tests/headerTabs.js +++ /dev/null @@ -1,49 +0,0 @@ - -const assert = require('assert'); - -const MockApp = require('../../nodeMock/app'); -const { browser } = require('protractor'); -const BrowserUtil = require('../util/browserUtil'); -const HeaderPage = require('../../e2e/features/pageObjects/headerPage'); - -const OrganisationPage = require('../../e2e/features/pageObjects/viewOrganisationPage'); -const UsersPage = require('../../e2e/features/pageObjects/viewUserPage'); -const ViewOrganisationPage = require('../../e2e/features/pageObjects/viewOrganisationPage'); -const unassignedCasesPage = require('../../e2e/features/pageObjects/unassignedCasePage'); - -describe('Header Tabs', function () { - headerPage = new HeaderPage(); - organisationPage = new OrganisationPage(); - usersPage = new UsersPage(); - - beforeEach(async function (done) { - MockApp.init(); - done(); - }); - afterEach(async function (done) { - await MockApp.stopServer(); - done(); - }); - - it('Organisation page', async function () { - await MockApp.startServer(); - await BrowserUtil.browserInitWithAuth(); - await headerPage.clickHeaderTabWithtext('Organisation'); - expect(await organisationPage.amOnPage(), 'Not on rganisation page').to.be.true; - }); - - it('users page', async function () { - await MockApp.startServer(); - await BrowserUtil.browserInitWithAuth(); - await headerPage.clickHeaderTabWithtext('Users'); - expect(await usersPage.amOnPage(), 'Not on Users page').to.be.true; - }); - - it('Unassigned cases', async function () { - await MockApp.startServer(); - await BrowserUtil.browserInitWithAuth(); - await headerPage.clickHeaderTabWithtext('Unassigned cases'); - expect(await unassignedCasesPage.amOnPage(), 'Not on Unassigned cases page').to.be.true; - }); -}); - diff --git a/test_codecept/ngIntegration/tests/stepDefinitions/common.steps.js b/test_codecept/ngIntegration/tests/stepDefinitions/common.steps.js new file mode 100644 index 000000000..9a058b2ce --- /dev/null +++ b/test_codecept/ngIntegration/tests/stepDefinitions/common.steps.js @@ -0,0 +1,19 @@ + +const browserUtil = require('../../util/browserUtil'); +// const nodeAppMockData = require('../../../nodeMock/nodeApp/mockData'); +const CucumberReporter = require('../../../codeceptCommon/reportLogger'); +const BrowserWaits = require('../../../e2e/support/customWaits'); +const headerpage = require('../../../e2e/features/pageObjects/headerPage'); + + +// const ccdApi = require('../../../nodeMock/ccd/ccdApi'); + + + Then('I validate session storage has key {string}', async function(key){ + await BrowserWaits.retryWithActionCallback(async () => { + const sessionStorageVal = await browserUtil.getFromSessionStorage(key); + expect(sessionStorageVal !== null && sessionStorageVal !== undefined, `Session stoarge does not have ${key} key ${sessionStorageVal}`).to.be.true; + }); + + }); + diff --git a/test_codecept/ngIntegration/tests/stepDefinitions/mockBrowser.steps.js b/test_codecept/ngIntegration/tests/stepDefinitions/mockBrowser.steps.js new file mode 100644 index 000000000..f037e0450 --- /dev/null +++ b/test_codecept/ngIntegration/tests/stepDefinitions/mockBrowser.steps.js @@ -0,0 +1,38 @@ +const idamLogin = require("../../util/idamLogin"); + +const mockClient = require('../../../backendMock/client/index') +Given('I set MOCK browser cookies', async function () { + + let cookies = cookieString.replace(" ", "") + cookies = cookies + ";xui-webapp=" + sessionToken; + + let cookiesList = cookies.split(";"); + cookiesList = cookiesList.map(cookie => { + const kv = cookie.split('=') + return { name: kv[0].trim(), value:kv[1].trim()} + }) + + browser.driver.manage().setCookies(cookiesList); +}); + + +Given('I set debug browser user details', async function (datatable) { + const rowsHash = datatable.parse().rowsHash() + let cookies = cookieString.replace(" ","") + cookies = cookies + ";xui-webapp=" + sessionToken + const userDetails = await idamLogin.getUserDetailsWithCookieString(cookies) + const properties = rowsHash; + for (const key of Object.keys(properties)) { + if (key === 'roles') { + userDetails.userInfo[key] = properties[key].split(',').map(v => v.trim()) + } else { + userDetails.userInfo[key] = properties[key] + } + + } + const auth = await browser.driver.manage().getCookie('__auth__') + await mockClient.updateAuthSessionWithUserInfo(auth.value, userDetails.userInfo); +}); + +const cookieString = "exui-preferred-language=en; xui-webapp=s%3AEibG994JziltsqsRqLVHk7RYeDeOBr9h.5U0%2FMGgv%2FU%2F6eNHz3LCp%2FR8p3KewzkVdNRk37J7pV94; __userid__=52b5a4b0-a69f-41c5-a89f-84b884d7a04d; __auth__=eyJ0eXAiOiJKV1QiLCJraWQiOiIxZXIwV1J3Z0lPVEFGb2pFNHJDL2ZiZUt1M0k9IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJDUkRfZnVuY190ZXN0X2FhdF9zdGN3QGp1c3RpY2UuZ292LnVrIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6IjljZDZhN2U0LTc3NmQtNDJjNC1hOWU1LTM2YmVhODdkNTM3Ny03NzI3MTU5OCIsInN1Ym5hbWUiOiJDUkRfZnVuY190ZXN0X2FhdF9zdGN3QGp1c3RpY2UuZ292LnVrIiwiaXNzIjoiaHR0cHM6Ly9mb3JnZXJvY2stYW0uc2VydmljZS5jb3JlLWNvbXB1dGUtaWRhbS1hYXQyLmludGVybmFsOjg0NDMvb3BlbmFtL29hdXRoMi9yZWFsbXMvcm9vdC9yZWFsbXMvaG1jdHMiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiI0WUdyQ0FTVWxfRHQ1WGRoSzFSWmdLazBLUVUiLCJub25jZSI6IlVxNVZDZTdBaG9VaUw5clkyQjZOdEdfRWpva2ZKLVBHbzl3SnZQTXZpWWciLCJhdWQiOiJ4dWl3ZWJhcHAiLCJuYmYiOjE2OTA1NDU0MzEsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwicm9sZXMiLCJjcmVhdGUtdXNlciIsIm1hbmFnZS11c2VyIiwic2VhcmNoLXVzZXIiXSwiYXV0aF90aW1lIjoxNjkwNTQ1NDMxLCJyZWFsbSI6Ii9obWN0cyIsImV4cCI6MTY5MDU3NDIzMSwiaWF0IjoxNjkwNTQ1NDMxLCJleHBpcmVzX2luIjoyODgwMCwianRpIjoiWXJib0FJcDlVQWl2cEtwbzdDX3V6Ulp2T2cwIn0.IHXxQUVQ09qt6FhGPfieDa-3WnugbRj0v8j-sD573M2NAMybhaAQN9AGCVMjDms-7YG1Gnc9mBQxK89meSD0TPmZz5qYfQKriHWRKqbltGGsamsmPX1OcekJLeBk8dJzntIWq4XVJIr7RToZAuCASlZY6BFwj7005NqIBzbdyMXd4ZPmL2yFUoEuC1yxCGt8HSNwZkzFKVUqJpVT0-8j5q2fIWAP9ZLNkPXE43hMBkrHen9WmUpv6p1K8yKvy4-o8synSUImnF6487GUPDzvi6_qcwAOR0xmZp354AwYsKIZNK6jzd-WxLtMXk921BD1gk_xxHcscWnJaLLjTRyIMA; XSRF-TOKEN=kHk4s3wL-fvQc2Jpur93du7xIEJ6l4MQZswM" +const sessionToken = "s%3AEibG994JziltsqsRqLVHk7RYeDeOBr9h.5U0%2FMGgv%2FU%2F6eNHz3LCp%2FR8p3KewzkVdNRk37J7pV94" diff --git a/test_codecept/ngIntegration/tests/stepDefinitions/nodeMock.steps.js b/test_codecept/ngIntegration/tests/stepDefinitions/nodeMock.steps.js new file mode 100644 index 000000000..351b550d9 --- /dev/null +++ b/test_codecept/ngIntegration/tests/stepDefinitions/nodeMock.steps.js @@ -0,0 +1,215 @@ + +const BrowserWaits = require('../../../e2e/support/customWaits'); +const browserUtil = require('../../util/browserUtil'); +const CucumberReporter = require('../../../codeceptCommon/reportLogger'); + +const reportLogger = require('../../../codeceptCommon/reportLogger'); + +const idamlogin = require('../../util/idamLogin') + +const mockClient = require('../../../backendMock/client/index') + +async function mockLoginWithRoles(roles) { + idamlogin.withCredentials('lukesuperuserxui@mailnesia.com', 'Monday01') + + + await browser.get('http://localhost:3000/get-help'); + let userDetails = null; + + await BrowserWaits.retryWithActionCallback(async () => { + await idamlogin.do(); + userDetails = idamlogin.userDetailsResponse.details.data; + const sessionUserName = userDetails ? userDetails.email : ''; + if (sessionUserName !== 'lukesuperuserxui@mailnesia.com') { + throw new Error('session not updated with user, retrying'); + } + + }) + + await BrowserWaits.retryWithActionCallback(async () => { + await browser.driver.manage().setCookies(idamlogin.xuiCallbackResponse.details.setCookies) + await browser.get('http://localhost:3000/'); + + }) + + const authCookies = idamlogin.xuiCallbackResponse.details.setCookies + const authCookie = authCookies.find(cookie => cookie.name === '__auth__') + await browser.sleep(10) + await mockClient.updateAuthSessionWithRoles(authCookie.value, roles) + return await idamlogin.getUserDetails() + + // await browser.get('http://localhost:3000/'); +} + + +Given('I set MOCK with user roles', async function (rolesTable) { + const roles = []; + const rolesTablerows = rolesTable.parse().rows(); + for (const row of rolesTablerows) { + roles.push(row[0]); + } + await mockLoginWithRoles(roles) + + }); + + + Given('I init MockApp', async function () { + // MockApp.init(); + }); + + Given('I start MockApp', async function () { + const userDetails = await idamlogin.getUserDetails(); + reportLogger.AddJson(userDetails) + // await workAllocationMockData.applyToSession(); + + }); + + Given('I stop MockApp', async function () { + // await MockApp.stopServer(); + }); + + Given('I restart MockApp', async function () { + // await MockApp.stopServer(); + // await MockApp.startServer(); + }); + + // When('I set MOCK with user roles', async function(rolesTable){ + // const roles = []; + // const rolesTablerows = rolesTable.parse().rows(); + // for (const row of rolesTablerows){ + // roles.push(row[0]); + // } + + // const userDetails = nodeAppMockData.getUserDetailsWithRoles(roles); + + // }); + + Given('I set MOCK request {string} intercept with reference {string}', async function(url,reference){ + // global.scenarioData[reference] = null; + // MockApp.addIntercept(url,(req,res,next) => { + // CucumberReporter.AddMessage(`Intercepted: ${url}`) + // CucumberReporter.AddJson(req.body) + // global.scenarioData[reference] = req.body; + // next(); + // }) + }); + + Given('I set MOCK request {string} response log to report', async function (url) { + // MockApp.addIntercept(url, (req, res, next) => { + // let send = res.send; + // res.send = function (body) { + // CucumberReporter.AddMessage(` ------------------------------Mock response intercept from server with port "${MockApp.serverPort }" ---------------------------`); + // CucumberReporter.AddMessage('Intercept response on MOCK api ' + url); + // CucumberReporter.AddMessage('response code ' + res.statusCode); + // try{ + // CucumberReporter.AddJson(body) + // }catch(err){ + // CucumberReporter.AddMessage(body) + // } + // CucumberReporter.AddMessage('------------------------------Mock response intercept---------------------------'); + // send.call(this, body); + // } + // next(); + // }) + }); + + Given('I set MOCK request {string} intercept, hold response with reference {string}', async function (url,reference) { + // MockApp.addIntercept(url, (req, res, next) => { + // CucumberReporter.AddJson(req.body) + // let send = res.send; + // res.send = function (body) { + // CucumberReporter.AddMessage('Intercept response or api ' + url); + // CucumberReporter.AddJson(body) + // global.scenarioData[reference] = body; + // send.call(this, body); + // } + // next(); + // }) + }); + + Given('I reset reference {string} value to null', async function(reference){ + global.scenarioData[reference] = null; + }); + + Then('I verify reference {string} value is null', async function (reference){ + expect(global.scenarioData[reference] === null, `Assertion failed: ${reference} is not null`).to.be.true; + }); + + When('I wait for reference {string} value not null', async function(reference){ + // await BrowserWaits.retryWithActionCallback(async () => { + // expect(global.scenarioData[reference] !== null, `reference ${reference} is null`).to.be.true; + // try{ + // reportLogger.AddJson(global.scenarioData[reference]); + // }catch(err){ + // reportLogger.AddMessage(global.scenarioData[reference]); + // } + + // }); + }); + + Given('I set MOCK api method {string} endpoint {string} with error response code {int}', async function(apiMethod, apiEndpoint, responseCode){ + + switch (apiMethod.toLowerCase()){ + case 'get': + MockApp.onGet(apiEndpoint, (req, res) => { + reportLogger.AddMessage(`Mock response code returned ${responseCode}`); + res.status(responseCode).send({ error: "Test error from mock" }); + }); + break; + case 'post': + MockApp.onPost(apiEndpoint, (req, res) => { + reportLogger.AddMessage(`Mock response code returned ${responseCode}`); + res.status(responseCode).send({ error: "Test error from mock" }); + }); + break; + case 'put': + MockApp.onPut(apiEndpoint, (req, res) => { + reportLogger.AddMessage(`Mock response code returned ${responseCode}`); + res.status(responseCode).send({ error: "Test error from mock" }); + }); + break; + case 'delete': + MockApp.onDelete(apiEndpoint, (req, res) => { + reportLogger.AddMessage(`Mock response code returned ${responseCode}`); + res.status(responseCode).send({ error: "Test error from mock" }); + }); + break; + + default: + throw new Error("api method provided not recognised " + apiMethod); + } + + + }); + + + Given('I set MOCK find person response for jurisdictions', async function(datatable){ + const personsConfigHashes = datatable.parse().hashes(); + + for (const person of personsConfigHashes) { + + if (person.domain === 'Judicial'){ + const judge = JSON.parse(JSON.stringify(workAllocationMockData.judgeUsers[0])); + judge.sidam_id = person.id; + judge.email_id = person.email; + judge.full_name = person.name; + + workAllocationMockData.judgeUsers.push(judge); + } else if (person.domain === 'Legal Ops'){ + const cw = JSON.parse(JSON.stringify(workAllocationMockData.caseWorkersList[0])); + const fullNameArr = person.name.split(" "); + cw.idamId = person.id; + cw.email = person.email; + cw.firstName = fullNameArr[0]; + cw.lastName = fullNameArr[1]; + + workAllocationMockData.addCaseworker(cw,'IA'); + + } + + + } + + + + }); diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index ae7791df3..3a6271b22 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1 +1 @@ -{"actions":[],"advisories":{"1088208":{"findings":[{"version":"0.8.4","paths":["git-rev-sync>shelljs"]}],"metadata":null,"vulnerable_versions":"<0.8.5","module_name":"shelljs","severity":"moderate","github_advisory_id":"GHSA-64g7-mvw6-v9qj","cves":[],"access":"public","patched_versions":">=0.8.5","cvss":{"score":0,"vectorString":null},"updated":"2023-01-11T05:03:39.000Z","recommendation":"Upgrade to version 0.8.5 or later","cwe":["CWE-269"],"found_by":null,"deleted":null,"id":1088208,"references":"- https://github.com/shelljs/shelljs/security/advisories/GHSA-64g7-mvw6-v9qj\n- https://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c/\n- https://github.com/advisories/GHSA-64g7-mvw6-v9qj","created":"2022-01-14T21:09:50.000Z","reported_by":null,"title":"Improper Privilege Management in shelljs","npm_advisory_id":null,"overview":"### Impact\nOutput from the synchronous version of `shell.exec()` may be visible to other users on the same system. You may be affected if you execute `shell.exec()` in multi-user Mac, Linux, or WSL environments, or if you execute `shell.exec()` as the root user.\n\nOther shelljs functions (including the asynchronous version of `shell.exec()`) are not impacted.\n\n### Patches\nPatched in shelljs 0.8.5\n\n### Workarounds\nRecommended action is to upgrade to 0.8.5.\n\n### References\nhttps://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c/\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Ask at https://github.com/shelljs/shelljs/issues/1058\n* Open an issue at https://github.com/shelljs/shelljs/issues/new\n","url":"https://github.com/advisories/GHSA-64g7-mvw6-v9qj"},"1088659":{"findings":[{"version":"2.1.2","paths":["@hmcts/nodejs-healthcheck>superagent>cookiejar"]}],"metadata":null,"vulnerable_versions":"<2.1.4","module_name":"cookiejar","severity":"moderate","github_advisory_id":"GHSA-h452-7996-h45h","cves":["CVE-2022-25901"],"access":"public","patched_versions":">=2.1.4","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-01-23T16:59:53.000Z","recommendation":"Upgrade to version 2.1.4 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1088659,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25901\n- https://github.com/bmeck/node-cookiejar/pull/39\n- https://github.com/bmeck/node-cookiejar/pull/39/commits/eaa00021caf6ae09449dde826108153b578348e5\n- https://security.snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-3176681\n- https://security.snyk.io/vuln/SNYK-JS-COOKIEJAR-3149984\n- https://github.com/bmeck/node-cookiejar/blob/master/cookiejar.js#23L73\n- https://github.com/advisories/GHSA-h452-7996-h45h","created":"2023-01-18T06:31:03.000Z","reported_by":null,"title":"cookiejar Regular Expression Denial of Service via Cookie.parse function","npm_advisory_id":null,"overview":"Versions of the package cookiejar before 2.1.4 are vulnerable to Regular Expression Denial of Service (ReDoS) via the `Cookie.parse` function and other aspects of the API, which use an insecure regular expression for parsing cookie values. Applications could be stalled for extended periods of time if untrusted input is passed to cookie values or attempted to parse from request headers.\n\nProof of concept:\n\n```\nts\\nconst { CookieJar } = require(\"cookiejar\");\n\nconst jar = new CookieJar();\n\nconst start = performance.now();\n\nconst attack = \"a\" + \"t\".repeat(50_000);\njar.setCookie(attack);\n\nconsole.log(`CookieJar.setCookie(): ${performance.now() - start}ms`);\n\n```\n\n```\nCookieJar.setCookie(): 2963.214399999939ms\n```","url":"https://github.com/advisories/GHSA-h452-7996-h45h"},"1088948":{"findings":[{"version":"6.7.1","paths":["@hmcts/rpx-xui-node-lib>openid-client>got"]}],"metadata":null,"vulnerable_versions":"<11.8.5","module_name":"got","severity":"moderate","github_advisory_id":"GHSA-pfrx-2q88-qq97","cves":["CVE-2022-33987"],"access":"public","patched_versions":">=11.8.5","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"},"updated":"2023-01-27T05:05:01.000Z","recommendation":"Upgrade to version 11.8.5 or later","cwe":[],"found_by":null,"deleted":null,"id":1088948,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-33987\n- https://github.com/sindresorhus/got/pull/2047\n- https://github.com/sindresorhus/got/compare/v12.0.3...v12.1.0\n- https://github.com/sindresorhus/got/commit/861ccd9ac2237df762a9e2beed7edd88c60782dc\n- https://github.com/sindresorhus/got/releases/tag/v11.8.5\n- https://github.com/sindresorhus/got/releases/tag/v12.1.0\n- https://github.com/advisories/GHSA-pfrx-2q88-qq97","created":"2022-06-19T00:00:21.000Z","reported_by":null,"title":"Got allows a redirect to a UNIX socket","npm_advisory_id":null,"overview":"The got package before 11.8.5 and 12.1.0 for Node.js allows a redirect to a UNIX socket.","url":"https://github.com/advisories/GHSA-pfrx-2q88-qq97"},"1089067":{"findings":[{"version":"0.2.3","paths":["class-transformer"]}],"metadata":null,"vulnerable_versions":"<0.3.1","module_name":"class-transformer","severity":"moderate","github_advisory_id":"GHSA-6gp3-h3jj-prx4","cves":["CVE-2020-7637"],"access":"public","patched_versions":">=0.3.1","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"},"updated":"2023-01-27T05:08:29.000Z","recommendation":"Upgrade to version 0.3.1 or later","cwe":["CWE-915","CWE-1321"],"found_by":null,"deleted":null,"id":1089067,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-7637\n- https://github.com/typestack/class-transformer/blob/a650d9f490573443f62508bc063b857bcd5e2525/src/ClassTransformer.ts#L29-L31,\n- https://snyk.io/vuln/SNYK-JS-CLASSTRANSFORMER-564431\n- https://github.com/typestack/class-transformer/commit/8f04eb9db02de708f1a20f6f2d2bb309b2fed01e\n- https://github.com/advisories/GHSA-6gp3-h3jj-prx4","created":"2020-04-07T15:47:40.000Z","reported_by":null,"title":"Prototype pollution in class-transformer","npm_advisory_id":null,"overview":"class-transformer through 0.2.3 is vulnerable to Prototype Pollution. The 'classToPlainFromExist' function could be tricked into adding or modifying properties of 'Object.prototype' using a '__proto__' payload.","url":"https://github.com/advisories/GHSA-6gp3-h3jj-prx4"},"1089189":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":"<1.25.0","module_name":"prismjs","severity":"moderate","github_advisory_id":"GHSA-hqhp-5p83-hx96","cves":["CVE-2021-3801"],"access":"public","patched_versions":">=1.25.0","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-01-29T05:02:55.000Z","recommendation":"Upgrade to version 1.25.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089189,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3801\n- https://github.com/prismjs/prism/commit/0ff371bb4775a131634f47d0fe85794c547232f9\n- https://huntr.dev/bounties/8c16ab31-6eb6-46d1-b9a4-387222fe1b8a\n- https://github.com/advisories/GHSA-hqhp-5p83-hx96","created":"2021-09-20T20:44:48.000Z","reported_by":null,"title":"prismjs Regular Expression Denial of Service vulnerability","npm_advisory_id":null,"overview":"Prism is a syntax highlighting library. The prismjs package is vulnerable to ReDoS (regular expression denial of service). An attacker that is able to provide a crafted HTML comment as input may cause an application to consume an excessive amount of CPU.","url":"https://github.com/advisories/GHSA-hqhp-5p83-hx96"},"1089196":{"findings":[{"version":"3.0.2","paths":["redis","@hmcts/rpx-xui-node-lib>redis"]}],"metadata":null,"vulnerable_versions":">=2.6.0 <3.1.1","module_name":"redis","severity":"high","github_advisory_id":"GHSA-35q2-47q7-3pc3","cves":["CVE-2021-29469"],"access":"public","patched_versions":">=3.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-01-29T05:02:31.000Z","recommendation":"Upgrade to version 3.1.1 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089196,"references":"- https://github.com/NodeRedis/node-redis/security/advisories/GHSA-35q2-47q7-3pc3\n- https://nvd.nist.gov/vuln/detail/CVE-2021-29469\n- https://github.com/NodeRedis/node-redis/commit/2d11b6dc9b9774464a91fb4b448bad8bf699629e\n- https://github.com/NodeRedis/node-redis/releases/tag/v3.1.1\n- https://security.netapp.com/advisory/ntap-20210611-0010/\n- https://github.com/advisories/GHSA-35q2-47q7-3pc3","created":"2021-04-27T15:56:03.000Z","reported_by":null,"title":"Node-Redis potential exponential regex in monitor mode","npm_advisory_id":null,"overview":"### Impact\nWhen a client is in monitoring mode, the regex begin used to detected monitor messages could cause exponential backtracking on some strings. This issue could lead to a denial of service.\n\n### Patches\nThe problem was fixed in commit [`2d11b6d`](https://github.com/NodeRedis/node-redis/commit/2d11b6dc9b9774464a91fb4b448bad8bf699629e) and was released in version `3.1.1`.\n\n### References\n#1569 (GHSL-2021-026)","url":"https://github.com/advisories/GHSA-35q2-47q7-3pc3"},"1089270":{"findings":[{"version":"2.7.4","paths":["ejs"]}],"metadata":null,"vulnerable_versions":"<3.1.7","module_name":"ejs","severity":"critical","github_advisory_id":"GHSA-phwq-j96m-2c2q","cves":["CVE-2022-29078"],"access":"public","patched_versions":">=3.1.7","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-01-30T05:02:57.000Z","recommendation":"Upgrade to version 3.1.7 or later","cwe":["CWE-74"],"found_by":null,"deleted":null,"id":1089270,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-29078\n- https://eslam.io/posts/ejs-server-side-template-injection-rce/\n- https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf\n- https://github.com/mde/ejs/releases\n- https://security.netapp.com/advisory/ntap-20220804-0001/\n- https://github.com/advisories/GHSA-phwq-j96m-2c2q","created":"2022-04-26T00:00:40.000Z","reported_by":null,"title":"ejs template injection vulnerability","npm_advisory_id":null,"overview":"The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).","url":"https://github.com/advisories/GHSA-phwq-j96m-2c2q"},"1089434":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<=8.5.1","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-8cf7-32gw-wr33","cves":["CVE-2022-23539"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":0,"vectorString":null},"updated":"2023-01-31T05:01:09.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-327"],"found_by":null,"deleted":null,"id":1089434,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-8cf7-32gw-wr33\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23539\n- https://github.com/advisories/GHSA-8cf7-32gw-wr33","created":"2022-12-22T03:32:22.000Z","reported_by":null,"title":"jsonwebtoken unrestricted key type could lead to legacy keys usage ","npm_advisory_id":null,"overview":"# Overview\n\nVersions `<=8.5.1` of `jsonwebtoken` library could be misconfigured so that legacy, insecure key types are used for signature verification. For example, DSA keys could be used with the RS256 algorithm. \n\n# Am I affected?\n\nYou are affected if you are using an algorithm and a key type other than the combinations mentioned below\n\n| Key type | algorithm |\n|----------|------------------------------------------|\n| ec | ES256, ES384, ES512 |\n| rsa | RS256, RS384, RS512, PS256, PS384, PS512 |\n| rsa-pss | PS256, PS384, PS512 |\n\nAnd for Elliptic Curve algorithms:\n\n| `alg` | Curve |\n|-------|------------|\n| ES256 | prime256v1 |\n| ES384 | secp384r1 |\n| ES512 | secp521r1 |\n\n# How do I fix it?\n\nUpdate to version 9.0.0. This version validates for asymmetric key type and algorithm combinations. Please refer to the above mentioned algorithm / key type combinations for the valid secure configuration. After updating to version 9.0.0, If you still intend to continue with signing or verifying tokens using invalid key type/algorithm value combinations, you’ll need to set the `allowInvalidAsymmetricKeyTypes` option to `true` in the `sign()` and/or `verify()` functions.\n\n# Will the fix impact my users?\n\nThere will be no impact, if you update to version 9.0.0 and you already use a valid secure combination of key type and algorithm. Otherwise, use the `allowInvalidAsymmetricKeyTypes` option to `true` in the `sign()` and `verify()` functions to continue usage of invalid key type/algorithm combination in 9.0.0 for legacy compatibility. \n\n","url":"https://github.com/advisories/GHSA-8cf7-32gw-wr33"},"1089512":{"findings":[{"version":"6.2.1","paths":["log4js"]}],"metadata":null,"vulnerable_versions":"<6.4.0","module_name":"log4js","severity":"moderate","github_advisory_id":"GHSA-82v2-mx6x-wq7q","cves":["CVE-2022-21704"],"access":"public","patched_versions":">=6.4.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"},"updated":"2023-02-01T05:01:40.000Z","recommendation":"Upgrade to version 6.4.0 or later","cwe":["CWE-276"],"found_by":null,"deleted":null,"id":1089512,"references":"- https://github.com/log4js-node/log4js-node/security/advisories/GHSA-82v2-mx6x-wq7q\n- https://github.com/log4js-node/log4js-node/pull/1141/commits/8042252861a1b65adb66931fdf702ead34fa9b76\n- https://github.com/log4js-node/streamroller/pull/87\n- https://github.com/log4js-node/log4js-node/blob/v6.4.0/CHANGELOG.md#640\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21704\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00014.html\n- https://github.com/advisories/GHSA-82v2-mx6x-wq7q","created":"2022-01-21T18:53:27.000Z","reported_by":null,"title":"Incorrect Default Permissions in log4js","npm_advisory_id":null,"overview":"### Impact\r\nDefault file permissions for log files created by the file, fileSync and dateFile appenders are world-readable (in unix). This could cause problems if log files contain sensitive information. This would affect any users that have not supplied their own permissions for the files via the mode parameter in the config.\r\n\r\n### Patches\r\nFixed by:\r\n* https://github.com/log4js-node/log4js-node/pull/1141\r\n* https://github.com/log4js-node/streamroller/pull/87\r\n\r\nReleased to NPM in log4js@6.4.0\r\n\r\n### Workarounds\r\nEvery version of log4js published allows passing the mode parameter to the configuration of file appenders, see the documentation for details.\r\n\r\n### References\r\n\r\nThanks to [ranjit-git](https://www.huntr.dev/users/ranjit-git) for raising the issue, and to @lamweili for fixing the problem.\r\n\r\n### For more information\r\nIf you have any questions or comments about this advisory:\r\n* Open an issue in [logj4s-node](https://github.com/log4js-node/log4js-node)\r\n* Ask a question in the [slack channel](https://join.slack.com/t/log4js-node/shared_invite/enQtODkzMDQ3MzExMDczLWUzZmY0MmI0YWI1ZjFhODY0YjI0YmU1N2U5ZTRkOTYyYzg3MjY5NWI4M2FjZThjYjdiOGM0NjU2NzBmYTJjOGI)\r\n* Email us at [gareth.nomiddlename@gmail.com](mailto:gareth.nomiddlename@gmail.com)\r\n","url":"https://github.com/advisories/GHSA-82v2-mx6x-wq7q"},"1089630":{"findings":[{"version":"1.5.0","paths":["@hmcts/rpx-xui-node-lib>passport-oauth2"]}],"metadata":null,"vulnerable_versions":"<1.6.1","module_name":"passport-oauth2","severity":"moderate","github_advisory_id":"GHSA-f794-r6xc-hf3v","cves":["CVE-2021-41580"],"access":"public","patched_versions":">=1.6.1","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"},"updated":"2023-02-01T05:06:26.000Z","recommendation":"Upgrade to version 1.6.1 or later","cwe":["CWE-287"],"found_by":null,"deleted":null,"id":1089630,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-41580\n- https://github.com/jaredhanson/passport-oauth2/pull/144\n- https://github.com/jaredhanson/passport-oauth2/commit/8e3bcdff145a2219033bd782fc517229fe3e05ea\n- https://github.com/jaredhanson/passport-oauth2/compare/v1.6.0...v1.6.1\n- https://medium.com/passportjs/no-access-token-no-service-7fb017c9e262\n- https://github.com/advisories/GHSA-f794-r6xc-hf3v","created":"2021-09-29T17:18:32.000Z","reported_by":null,"title":"Improper Access Control in passport-oauth2","npm_advisory_id":null,"overview":"The passport-oauth2 package before 1.6.1 for Node.js mishandles the error condition of failure to obtain an access token. This is exploitable in certain use cases where an OAuth identity provider uses an HTTP 200 status code for authentication-failure error reports, and an application grants authorization upon simply receiving the access token (i.e., does not try to use the token). NOTE: the passport-oauth2 vendor does not consider this a passport-oauth2 vulnerability.","url":"https://github.com/advisories/GHSA-f794-r6xc-hf3v"},"1089698":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-g973-978j-2c3p","cves":["CVE-2021-32014"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:05:54.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-345","CWE-400"],"found_by":null,"deleted":null,"id":1089698,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32014\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-g973-978j-2c3p","created":"2021-07-22T19:47:15.000Z","reported_by":null,"title":"Denial of Service in SheetJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (CPU consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js.","url":"https://github.com/advisories/GHSA-g973-978j-2c3p"},"1089699":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-3x9f-74h4-2fqr","cves":["CVE-2021-32012"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:06:10.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089699,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32012\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-3x9f-74h4-2fqr","created":"2021-07-22T19:48:17.000Z","reported_by":null,"title":"Denial of Service in SheetJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (memory consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js (issue 1 of 2).","url":"https://github.com/advisories/GHSA-3x9f-74h4-2fqr"},"1089700":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-8vcr-vxm8-293m","cves":["CVE-2021-32013"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:06:00.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089700,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32013\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-8vcr-vxm8-293m","created":"2021-07-22T19:48:13.000Z","reported_by":null,"title":"Denial of Service in SheetsJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (memory consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js (issue 2 of 2).","url":"https://github.com/advisories/GHSA-8vcr-vxm8-293m"},"1089716":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":"<1.24.0","module_name":"prismjs","severity":"high","github_advisory_id":"GHSA-gj77-59wh-66hg","cves":["CVE-2021-32723"],"access":"public","patched_versions":">=1.24.0","cvss":{"score":7.4,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H"},"updated":"2023-02-01T05:05:49.000Z","recommendation":"Upgrade to version 1.24.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089716,"references":"- https://github.com/PrismJS/prism/security/advisories/GHSA-gj77-59wh-66hg\n- https://github.com/PrismJS/prism/pull/2688\n- https://github.com/PrismJS/prism/pull/2774\n- https://github.com/PrismJS/prism/commit/d85e30da6755fdbe7f8559f8e75d122297167018\n- https://nvd.nist.gov/vuln/detail/CVE-2021-32723\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-gj77-59wh-66hg","created":"2021-06-28T18:33:18.000Z","reported_by":null,"title":"Regular Expression Denial of Service (ReDoS) in Prism","npm_advisory_id":null,"overview":"Some languages before 1.24.0 are vulnerable to Regular Expression Denial of Service (ReDoS).\n\n### Impact\n\nWhen Prism is used to highlight untrusted (user-given) text, an attacker can craft a string that will take a very very long time to highlight. Do not use the following languages to highlight untrusted text.\n\n- ASCIIDoc\n- ERB\n\nOther languages are __not__ affected and can be used to highlight untrusted text.\n\n### Patches\nThis problem has been fixed in Prism v1.24.\n\n### References\n\n- PrismJS/prism#2774\n- PrismJS/prism#2688\n","url":"https://github.com/advisories/GHSA-gj77-59wh-66hg"},"1090424":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":">=1.14.0 <1.27.0","module_name":"prismjs","severity":"high","github_advisory_id":"GHSA-3949-f494-cm99","cves":["CVE-2022-23647"],"access":"public","patched_versions":">=1.27.0","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:L"},"updated":"2023-02-03T05:06:26.000Z","recommendation":"Upgrade to version 1.27.0 or later","cwe":["CWE-79"],"found_by":null,"deleted":null,"id":1090424,"references":"- https://github.com/PrismJS/prism/security/advisories/GHSA-3949-f494-cm99\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23647\n- https://github.com/PrismJS/prism/pull/3341\n- https://github.com/PrismJS/prism/commit/e002e78c343154e1c0ddf9d6a0bb85689e1a5c7c\n- https://github.com/advisories/GHSA-3949-f494-cm99","created":"2022-02-22T19:32:18.000Z","reported_by":null,"title":"Cross-site Scripting in Prism","npm_advisory_id":null,"overview":"### Impact\nPrism's [Command line plugin](https://prismjs.com/plugins/command-line/) can be used by attackers to achieve an XSS attack. The Command line plugin did not properly escape its output, leading to the input text being inserted into the DOM as HTML code.\n\nServer-side usage of Prism is not impacted. Websites that do not use the Command Line plugin are also not impacted.\n\n### Patches\nThis bug has been fixed in v1.27.0.\n\n### Workarounds\nDo not use the Command line plugin on untrusted inputs, or sanitized all code blocks (remove all HTML code text) from all code blocks that use the Command line plugin.\n\n### References\n- https://github.com/PrismJS/prism/pull/3341","url":"https://github.com/advisories/GHSA-3949-f494-cm99"},"1091087":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<=8.5.1","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-hjrf-2m68-5959","cves":["CVE-2022-23541"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L"},"updated":"2023-01-29T05:06:34.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-287"],"found_by":null,"deleted":null,"id":1091087,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-hjrf-2m68-5959\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23541\n- https://github.com/auth0/node-jsonwebtoken/releases/tag/v9.0.0\n- https://github.com/advisories/GHSA-hjrf-2m68-5959","created":"2022-12-22T03:33:19.000Z","reported_by":null,"title":"jsonwebtoken's insecure implementation of key retrieval function could lead to Forgeable Public/Private Tokens from RSA to HMAC","npm_advisory_id":null,"overview":"# Overview\n\nVersions `<=8.5.1` of `jsonwebtoken` library can be misconfigured so that passing a poorly implemented key retrieval function (referring to the `secretOrPublicKey` argument from the [readme link](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback)) will result in incorrect verification of tokens. There is a possibility of using a different algorithm and key combination in verification than the one that was used to sign the tokens. Specifically, tokens signed with an asymmetric public key could be verified with a symmetric HS256 algorithm. This can lead to successful validation of forged tokens. \n\n# Am I affected?\n\nYou will be affected if your application is supporting usage of both symmetric key and asymmetric key in jwt.verify() implementation with the same key retrieval function. \n\n# How do I fix it?\n \nUpdate to version 9.0.0.\n\n# Will the fix impact my users?\n\nThere is no impact for end users","url":"https://github.com/advisories/GHSA-hjrf-2m68-5959"},"1091430":{"findings":[{"version":"2.29.0","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>moment","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>moment"]}],"metadata":null,"vulnerable_versions":"<2.29.2","module_name":"moment","severity":"high","github_advisory_id":"GHSA-8hfj-j24r-96c4","cves":["CVE-2022-24785"],"access":"public","patched_versions":">=2.29.2","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"},"updated":"2023-03-20T22:35:12.000Z","recommendation":"Upgrade to version 2.29.2 or later","cwe":["CWE-22","CWE-27"],"found_by":null,"deleted":null,"id":1091430,"references":"- https://github.com/moment/moment/security/advisories/GHSA-8hfj-j24r-96c4\n- https://nvd.nist.gov/vuln/detail/CVE-2022-24785\n- https://github.com/moment/moment/commit/4211bfc8f15746be4019bba557e29a7ba83d54c5\n- https://www.tenable.com/security/tns-2022-09\n- https://security.netapp.com/advisory/ntap-20220513-0006/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6QIO6YNLTK2T7SPKDS4JEL45FANLNC2Q/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ORJX2LF6KMPIHP6B2P6KZIVKMLE3LVJ5/\n- https://lists.debian.org/debian-lts-announce/2023/01/msg00035.html\n- https://github.com/advisories/GHSA-8hfj-j24r-96c4","created":"2022-04-04T21:25:48.000Z","reported_by":null,"title":"Path Traversal: 'dir/../../filename' in moment.locale","npm_advisory_id":null,"overview":"### Impact\nThis vulnerability impacts npm (server) users of moment.js, especially if user provided locale string, eg `fr` is directly used to switch moment locale.\n\n### Patches\nThis problem is patched in 2.29.2, and the patch can be applied to all affected versions (from 1.0.1 up until 2.29.1, inclusive).\n\n### Workarounds\nSanitize user-provided locale name before passing it to moment.js.\n\n### References\n_Are there any links users can visit to find out more?_\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [moment repo](https://github.com/moment/moment)\n","url":"https://github.com/advisories/GHSA-8hfj-j24r-96c4"},"1091441":{"findings":[{"version":"2.29.0","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>moment","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>moment"]}],"metadata":null,"vulnerable_versions":">=2.18.0 <2.29.4","module_name":"moment","severity":"high","github_advisory_id":"GHSA-wc69-rhjr-hc9g","cves":["CVE-2022-31129"],"access":"public","patched_versions":">=2.29.4","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-03-20T23:29:32.000Z","recommendation":"Upgrade to version 2.29.4 or later","cwe":["CWE-400","CWE-1333"],"found_by":null,"deleted":null,"id":1091441,"references":"- https://github.com/moment/moment/security/advisories/GHSA-wc69-rhjr-hc9g\n- https://github.com/moment/moment/pull/6015#issuecomment-1152961973\n- https://github.com/moment/moment/commit/9a3b5894f3d5d602948ac8a02e4ee528a49ca3a3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-31129\n- https://huntr.dev/bounties/f0952b67-f2ff-44a9-a9cd-99e0a87cb633/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6QIO6YNLTK2T7SPKDS4JEL45FANLNC2Q/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ORJX2LF6KMPIHP6B2P6KZIVKMLE3LVJ5/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/IWY24RJA3SBJGA5N4CU4VBPHJPPPJL5O/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZMX5YHELQVCGKKQVFXIYOTBMN23YYSRO/\n- https://security.netapp.com/advisory/ntap-20221014-0003/\n- https://lists.debian.org/debian-lts-announce/2023/01/msg00035.html\n- https://github.com/moment/moment/pull/6015/commits/4bbb9f3ccbe231de40207503f344fe5ce97584f4\n- https://github.com/moment/moment/pull/6015/commits/bfd4f2375d5c1a2106246721d693a9611dddfbfe\n- https://github.com/moment/moment/pull/6015/commits/dc0d180e90d8a84f7ff13572363330a22b3ea504\n- https://github.com/advisories/GHSA-wc69-rhjr-hc9g","created":"2022-07-06T18:38:49.000Z","reported_by":null,"title":"Moment.js vulnerable to Inefficient Regular Expression Complexity","npm_advisory_id":null,"overview":"### Impact\n\n* using string-to-date parsing in moment (more specifically rfc2822 parsing, which is tried by default) has quadratic (N^2) complexity on specific inputs\n* noticeable slowdown is observed with inputs above 10k characters\n* users who pass user-provided strings without sanity length checks to moment constructor are vulnerable to (Re)DoS attacks\n\n### Patches\nThe problem is patched in 2.29.4, the patch can be applied to all affected versions with minimal tweaking.\n\n### Workarounds\nIn general, given the proliferation of ReDoS attacks, it makes sense to limit the length of the user input to something sane, like 200 characters or less. I haven't seen legitimate cases of date-time strings longer than that, so all moment users who do pass a user-originating string to constructor are encouraged to apply such a rudimentary filter, that would help with this but also most future ReDoS vulnerabilities.\n\n### References\nThere is an excellent writeup of the issue here: https://github.com/moment/moment/pull/6015#issuecomment-1152961973=\n\n### Details\nThe issue is rooted in the code that removes legacy comments (stuff inside parenthesis) from strings during rfc2822 parsing. `moment(\"(\".repeat(500000))` will take a few minutes to process, which is unacceptable.","url":"https://github.com/advisories/GHSA-wc69-rhjr-hc9g"},"1091453":{"findings":[{"version":"0.8.4","paths":["git-rev-sync>shelljs"]}],"metadata":null,"vulnerable_versions":"<0.8.5","module_name":"shelljs","severity":"high","github_advisory_id":"GHSA-4rq4-32rv-6wp6","cves":["CVE-2022-0144"],"access":"public","patched_versions":">=0.8.5","cvss":{"score":7.1,"vectorString":"CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H"},"updated":"2023-03-21T20:10:17.000Z","recommendation":"Upgrade to version 0.8.5 or later","cwe":["CWE-269"],"found_by":null,"deleted":null,"id":1091453,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-0144\n- https://github.com/shelljs/shelljs/commit/d919d22dd6de385edaa9d90313075a77f74b338c\n- https://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c\n- https://github.com/advisories/GHSA-4rq4-32rv-6wp6","created":"2022-01-21T23:37:28.000Z","reported_by":null,"title":"Improper Privilege Management in shelljs","npm_advisory_id":null,"overview":"shelljs is vulnerable to Improper Privilege Management","url":"https://github.com/advisories/GHSA-4rq4-32rv-6wp6"},"1091470":{"findings":[{"version":"1.8.3","paths":["@pact-foundation/pact-node>underscore","@pact-foundation/pact>@pact-foundation/pact-node>underscore"]}],"metadata":null,"vulnerable_versions":">=1.3.2 <1.12.1","module_name":"underscore","severity":"critical","github_advisory_id":"GHSA-cf4h-3jhx-xvhq","cves":["CVE-2021-23358"],"access":"public","patched_versions":">=1.12.1","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-03-23T20:56:35.000Z","recommendation":"Upgrade to version 1.12.1 or later","cwe":["CWE-94"],"found_by":null,"deleted":null,"id":1091470,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-23358\n- https://github.com/jashkenas/underscore/pull/2917\n- https://github.com/jashkenas/underscore/commit/4c73526d43838ad6ab43a6134728776632adeb66\n- https://github.com/jashkenas/underscore/releases/tag/1.12.1\n- https://snyk.io/vuln/SNYK-JS-UNDERSCORE-1080984\n- https://www.npmjs.com/package/underscore\n- https://github.com/jashkenas/underscore/blob/master/modules/template.js%23L71\n- https://lists.debian.org/debian-lts-announce/2021/03/msg00038.html\n- https://www.debian.org/security/2021/dsa-4883\n- https://lists.apache.org/thread.html/r5df90c46f7000c4aab246e947f62361ecfb849c5a553dcdb0ef545e1@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/r770f910653772317b117ab4472b0a32c266ee4abbafda28b8a6f9306@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/raae088abdfa4fbd84e1d19d7a7ffe52bf8e426b83e6599ea9a734dba@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/rbc84926bacd377503a3f5c37b923c1931f9d343754488d94e6f08039@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/re69ee408b3983b43e9c4a82a9a17cbbf8681bb91a4b61b46f365aeaf@%3Cissues.cordova.apache.org%3E\n- https://www.tenable.com/security/tns-2021-14\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EOKATXXETD2PF3OR36Q5PD2VSVAR6J5Z/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FGEE7U4Z655A2MK5EW4UQQZ7B64XJWBV/\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1081504\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBJASHKENAS-1081505\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1081503\n- https://github.com/advisories/GHSA-cf4h-3jhx-xvhq","created":"2021-05-06T16:09:43.000Z","reported_by":null,"title":"Arbitrary Code Execution in underscore","npm_advisory_id":null,"overview":"The package `underscore` from 1.13.0-0 and before 1.13.0-2, from 1.3.2 and before 1.12.1 are vulnerable to Arbitrary Code Execution via the template function, particularly when a variable property is passed as an argument as it is not sanitized.","url":"https://github.com/advisories/GHSA-cf4h-3jhx-xvhq"},"1091472":{"findings":[{"version":"0.2.3","paths":["@pact-foundation/pact-node>request>http-signature>jsprim>json-schema","@pact-foundation/pact>@pact-foundation/pact-node>request>http-signature>jsprim>json-schema"]}],"metadata":null,"vulnerable_versions":"<0.4.0","module_name":"json-schema","severity":"critical","github_advisory_id":"GHSA-896r-f27r-55mw","cves":["CVE-2021-3918"],"access":"public","patched_versions":">=0.4.0","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-03-23T20:35:18.000Z","recommendation":"Upgrade to version 0.4.0 or later","cwe":["CWE-915","CWE-1321"],"found_by":null,"deleted":null,"id":1091472,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3918\n- https://github.com/kriszyp/json-schema/commit/22f146111f541d9737e832823699ad3528ca7741\n- https://huntr.dev/bounties/bb6ccd63-f505-4e3a-b55f-cd2662c261a9\n- https://github.com/kriszyp/json-schema/commit/b62f1da1ff5442f23443d6be6a92d00e65cba93a\n- https://github.com/kriszyp/json-schema/commit/f6f6a3b02d667aa4ba2d5d50cc19208c4462abfa\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00013.html\n- https://github.com/advisories/GHSA-896r-f27r-55mw","created":"2021-11-19T20:16:17.000Z","reported_by":null,"title":"json-schema is vulnerable to Prototype Pollution","npm_advisory_id":null,"overview":"json-schema before version 0.4.0 is vulnerable to Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').","url":"https://github.com/advisories/GHSA-896r-f27r-55mw"},"1092316":{"findings":[{"version":"4.1.0","paths":["@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>make-fetch-happen>http-cache-semantics","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>make-fetch-happen>http-cache-semantics","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics"]}],"metadata":null,"vulnerable_versions":"<4.1.1","module_name":"http-cache-semantics","severity":"high","github_advisory_id":"GHSA-rc47-6667-2j5j","cves":["CVE-2022-25881"],"access":"public","patched_versions":">=4.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-06-22T17:26:15.000Z","recommendation":"Upgrade to version 4.1.1 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1092316,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25881\n- https://github.com/kornelski/http-cache-semantics/blob/master/index.js%23L83\n- https://security.snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-3253332\n- https://security.snyk.io/vuln/SNYK-JS-HTTPCACHESEMANTICS-3248783\n- https://github.com/kornelski/http-cache-semantics/commit/560b2d8ef452bbba20ffed69dc155d63ac757b74\n- https://security.netapp.com/advisory/ntap-20230622-0008/\n- https://github.com/advisories/GHSA-rc47-6667-2j5j","created":"2023-01-31T06:30:26.000Z","reported_by":null,"title":"http-cache-semantics vulnerable to Regular Expression Denial of Service","npm_advisory_id":null,"overview":"http-cache semantics contains an Inefficient Regular Expression Complexity , leading to Denial of Service. This affects versions of the package http-cache-semantics before 4.1.1. The issue can be exploited via malicious request header values sent to a server, when that server reads the cache policy from the request using this library.","url":"https://github.com/advisories/GHSA-rc47-6667-2j5j"},"1092423":{"findings":[{"version":"2.6.3","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>winston>async","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>winston>async"]}],"metadata":null,"vulnerable_versions":">=2.0.0 <2.6.4","module_name":"async","severity":"high","github_advisory_id":"GHSA-fwr7-v2mv-hh25","cves":["CVE-2021-43138"],"access":"public","patched_versions":">=2.6.4","cvss":{"score":7.8,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"},"updated":"2023-07-07T18:19:47.000Z","recommendation":"Upgrade to version 2.6.4 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1092423,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-43138\n- https://github.com/caolan/async/commit/e1ecdbf79264f9ab488c7799f4c76996d5dca66d\n- https://github.com/caolan/async/blob/master/lib/internal/iterator.js\n- https://github.com/caolan/async/blob/master/lib/mapValuesLimit.js\n- https://jsfiddle.net/oz5twjd9/\n- https://github.com/caolan/async/pull/1828\n- https://github.com/caolan/async/commit/8f7f90342a6571ba1c197d747ebed30c368096d2\n- https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md#v264\n- https://github.com/caolan/async/compare/v2.6.3...v2.6.4\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MTEUUTNIEBHGKUKKLNUZSV7IEP6IP3Q3/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UM6XJ73Q3NAM5KSGCOKJ2ZIA6GUWUJLK/\n- https://github.com/advisories/GHSA-fwr7-v2mv-hh25","created":"2022-04-07T00:00:17.000Z","reported_by":null,"title":"Prototype Pollution in async","npm_advisory_id":null,"overview":"A vulnerability exists in Async through 3.2.1 for 3.x and through 2.6.3 for 2.x (fixed in 3.2.2 and 2.6.4), which could let a malicious user obtain privileges via the `mapValues()` method.","url":"https://github.com/advisories/GHSA-fwr7-v2mv-hh25"},"1092470":{"findings":[{"version":"2.4.3","paths":["@pact-foundation/pact-node>request>tough-cookie","@pact-foundation/pact>@pact-foundation/pact-node>request>tough-cookie"]}],"metadata":null,"vulnerable_versions":"<4.1.3","module_name":"tough-cookie","severity":"moderate","github_advisory_id":"GHSA-72xf-g2v4-qvf3","cves":["CVE-2023-26136"],"access":"public","patched_versions":">=4.1.3","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"},"updated":"2023-07-11T13:44:36.000Z","recommendation":"Upgrade to version 4.1.3 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1092470,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-26136\n- https://github.com/salesforce/tough-cookie/issues/282\n- https://github.com/salesforce/tough-cookie/commit/12d474791bb856004e858fdb1c47b7608d09cf6e\n- https://github.com/salesforce/tough-cookie/releases/tag/v4.1.3\n- https://security.snyk.io/vuln/SNYK-JS-TOUGHCOOKIE-5672873\n- https://lists.debian.org/debian-lts-announce/2023/07/msg00010.html\n- https://github.com/advisories/GHSA-72xf-g2v4-qvf3","created":"2023-07-01T06:30:16.000Z","reported_by":null,"title":"tough-cookie Prototype Pollution vulnerability","npm_advisory_id":null,"overview":"Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in `rejectPublicSuffixes=false` mode. This issue arises from the manner in which the objects are initialized.","url":"https://github.com/advisories/GHSA-72xf-g2v4-qvf3"},"1092549":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<9.0.0","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-qwph-4952-7xr6","cves":["CVE-2022-23540"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":6.4,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:L"},"updated":"2023-07-14T22:03:14.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-287","CWE-327","CWE-347"],"found_by":null,"deleted":null,"id":1092549,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-qwph-4952-7xr6\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23540\n- https://github.com/advisories/GHSA-qwph-4952-7xr6","created":"2022-12-22T03:32:59.000Z","reported_by":null,"title":"jsonwebtoken vulnerable to signature validation bypass due to insecure default algorithm in jwt.verify()","npm_advisory_id":null,"overview":"# Overview\n\nIn versions <=8.5.1 of jsonwebtoken library, lack of algorithm definition and a falsy secret or key in the `jwt.verify()` function can lead to signature validation bypass due to defaulting to the `none` algorithm for signature verification.\n\n# Am I affected?\nYou will be affected if all the following are true in the `jwt.verify()` function:\n- a token with no signature is received\n- no algorithms are specified \n- a falsy (e.g. null, false, undefined) secret or key is passed \n\n# How do I fix it?\n \nUpdate to version 9.0.0 which removes the default support for the none algorithm in the `jwt.verify()` method. \n\n# Will the fix impact my users?\n\nThere will be no impact, if you update to version 9.0.0 and you don’t need to allow for the `none` algorithm. If you need 'none' algorithm, you have to explicitly specify that in `jwt.verify()` options.\n","url":"https://github.com/advisories/GHSA-qwph-4952-7xr6"},"1092636":{"findings":[{"version":"1.28.1","paths":["@hmcts/rpx-xui-node-lib>openid-client>jose"]}],"metadata":null,"vulnerable_versions":">=1.0.0 <=1.28.1","module_name":"jose","severity":"moderate","github_advisory_id":"GHSA-jv3g-j58f-9mq9","cves":["CVE-2022-36083"],"access":"public","patched_versions":">=1.28.2","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-07-21T21:33:36.000Z","recommendation":"Upgrade to version 1.28.2 or later","cwe":["CWE-400","CWE-834"],"found_by":null,"deleted":null,"id":1092636,"references":"- https://github.com/panva/jose/security/advisories/GHSA-jv3g-j58f-9mq9\n- https://nvd.nist.gov/vuln/detail/CVE-2022-36083\n- https://github.com/panva/jose/commit/03d6d013bf6e070e85adfe5731f526978e3e8e4d\n- https://github.com/panva/jose/releases/tag/v4.9.2\n- https://github.com/advisories/GHSA-jv3g-j58f-9mq9","created":"2022-09-16T17:44:42.000Z","reported_by":null,"title":"JOSE vulnerable to resource exhaustion via specifically crafted JWE","npm_advisory_id":null,"overview":"The PBKDF2-based JWE key management algorithms expect a JOSE Header Parameter named `p2c` ([PBES2 Count](https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2)), which determines how many PBKDF2 iterations must be executed in order to derive a CEK wrapping key. The purpose of this parameter is to intentionally slow down the key derivation function in order to make password brute-force and dictionary attacks more expensive.\n\nThis makes the PBES2 algorithms unsuitable for situations where the JWE is coming from an untrusted source: an adversary can intentionally pick an extremely high PBES2 Count value, that will initiate a CPU-bound computation that may take an unreasonable amount of time to finish.\n\n### Impact\n\nUnder certain conditions (see below) it is possible to have the user's environment consume unreasonable amount of CPU time.\n\n### Affected users\n\nThe impact is limited only to users utilizing the JWE decryption APIs with symmetric secrets to decrypt JWEs from untrusted parties who do not limit the accepted JWE Key Management Algorithms (`alg` Header Parameter) using the `keyManagementAlgorithms` (or `algorithms` in v1.x) decryption option or through other means.\n\nThe PBKDF2-based JWE Key Management Algorithm Identifiers are\n\n- `PBES2-HS256+A128KW`\n- `PBES2-HS384+A192KW`\n- `PBES2-HS512+A256KW`\n\ne.g.\n\n```js\nconst secret = new Uint8Array(16)\nconst jwe = '...' // JWE from an untrusted party\n\nawait jose.compactDecrypt(jwe, secret)\n```\n\nYou are NOT affected if any of the following applies to you\n\n- Your code does not use the JWE APIs\n- Your code only produces JWE tokens\n- Your code only decrypts JWEs using an asymmetric JWE Key Management Algorithm (this means you're providing an asymmetric key object to the JWE decryption API)\n- Your code only accepts JWEs produced by trusted sources\n- Your code limits the accepted JWE Key Management Algorithms using the `keyManagementAlgorithms` decryption option not including any of the PBKDF2-based JWE key management algorithms\n\n### Patches\n\n`v1.28.2`, `v2.0.6`, `v3.20.4`, and `v4.9.2` releases limit the maximum PBKDF2 iteration count to `10000` by default. It is possible to adjust this limit with a newly introduced `maxPBES2Count` decryption option.\n\n### Workarounds\n\nAll users should be able to upgrade given all stable semver major release lines have had new a patch release introduced which limits the PBKDF2 iteration count to `10000` by default. This removes the ability to craft JWEs that would consume unreasonable amount of CPU time.\n\nIf users are unable to upgrade their required library version they have two options depending on whether they expect to receive JWEs using any of the three PBKDF2-based JWE key management algorithms.\n\n- they can use the `keyManagementAlgorithms` decryption option to disable accepting PBKDF2 altogether\n- they can inspect the JOSE Header prior to using the decryption API and limit the PBKDF2 iteration count (`p2c` Header Parameter)\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an discussion in the project's [repository](https://github.com/panva/jose/discussions/new?category=q-a&title=GHSA-jv3g-j58f-9mq9%20advisory%20question)\n* Email me at [panva.ip@gmail.com](mailto:panva.ip@gmail.com)\n","url":"https://github.com/advisories/GHSA-jv3g-j58f-9mq9"},"1092964":{"findings":[{"version":"0.7.0","paths":["ngx-md>marked"]}],"metadata":null,"vulnerable_versions":"<4.0.10","module_name":"marked","severity":"high","github_advisory_id":"GHSA-5v2h-r2cx-5xgj","cves":["CVE-2022-21681"],"access":"public","patched_versions":">=4.0.10","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-08-14T05:04:30.000Z","recommendation":"Upgrade to version 4.0.10 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1092964,"references":"- https://github.com/markedjs/marked/security/advisories/GHSA-5v2h-r2cx-5xgj\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21681\n- https://github.com/markedjs/marked/commit/8f806573a3f6c6b7a39b8cdb66ab5ebb8d55a5f5\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AIXDMC3CSHYW3YWVSQOXAWLUYQHAO5UX/\n- https://github.com/advisories/GHSA-5v2h-r2cx-5xgj","created":"2022-01-14T21:04:46.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in marked","npm_advisory_id":null,"overview":"### Impact\n\n_What kind of vulnerability is it?_\n\nDenial of service.\n\nThe regular expression `inline.reflinkSearch` may cause catastrophic backtracking against some strings.\nPoC is the following.\n\n```javascript\nimport * as marked from 'marked';\n\nconsole.log(marked.parse(`[x]: x\n\n\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](`));\n```\n\n_Who is impacted?_\n\nAnyone who runs untrusted markdown through marked and does not use a worker with a time limit.\n\n### Patches\n\n_Has the problem been patched?_\n\nYes\n\n_What versions should users upgrade to?_\n\n4.0.10\n\n### Workarounds\n\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\nDo not run untrusted markdown through marked or run marked on a [worker](https://marked.js.org/using_advanced#workers) thread and set a reasonable time limit to prevent draining resources.\n\n### References\n\n_Are there any links users can visit to find out more?_\n\n- https://marked.js.org/using_advanced#workers\n- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [marked](https://github.com/markedjs/marked)\n","url":"https://github.com/advisories/GHSA-5v2h-r2cx-5xgj"},"1092969":{"findings":[{"version":"0.7.0","paths":["ngx-md>marked"]}],"metadata":null,"vulnerable_versions":"<4.0.10","module_name":"marked","severity":"high","github_advisory_id":"GHSA-rrrm-qjm4-v8hf","cves":["CVE-2022-21680"],"access":"public","patched_versions":">=4.0.10","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-08-14T05:03:59.000Z","recommendation":"Upgrade to version 4.0.10 or later","cwe":["CWE-400","CWE-1333"],"found_by":null,"deleted":null,"id":1092969,"references":"- https://github.com/markedjs/marked/security/advisories/GHSA-rrrm-qjm4-v8hf\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21680\n- https://github.com/markedjs/marked/commit/c4a3ccd344b6929afa8a1d50ac54a721e57012c0\n- https://github.com/markedjs/marked/releases/tag/v4.0.10\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AIXDMC3CSHYW3YWVSQOXAWLUYQHAO5UX/\n- https://github.com/advisories/GHSA-rrrm-qjm4-v8hf","created":"2022-01-14T21:04:41.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in marked","npm_advisory_id":null,"overview":"### Impact\n\n_What kind of vulnerability is it?_\n\nDenial of service.\n\nThe regular expression `block.def` may cause catastrophic backtracking against some strings.\nPoC is the following.\n\n```javascript\nimport * as marked from \"marked\";\n\nmarked.parse(`[x]:${' '.repeat(1500)}x ${' '.repeat(1500)} x`);\n```\n\n_Who is impacted?_\n\nAnyone who runs untrusted markdown through marked and does not use a worker with a time limit.\n\n### Patches\n\n_Has the problem been patched?_\n\nYes\n\n_What versions should users upgrade to?_\n\n4.0.10\n\n### Workarounds\n\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\nDo not run untrusted markdown through marked or run marked on a [worker](https://marked.js.org/using_advanced#workers) thread and set a reasonable time limit to prevent draining resources.\n\n### References\n\n_Are there any links users can visit to find out more?_\n\n- https://marked.js.org/using_advanced#workers\n- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [marked](https://github.com/markedjs/marked)\n","url":"https://github.com/advisories/GHSA-rrrm-qjm4-v8hf"},"1092972":{"findings":[{"version":"2.88.2","paths":["@pact-foundation/pact-node>request","@pact-foundation/pact>@pact-foundation/pact-node>request"]}],"metadata":null,"vulnerable_versions":"<=2.88.2","module_name":"request","severity":"moderate","github_advisory_id":"GHSA-p8p7-x288-28g6","cves":["CVE-2023-28155"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":6.1,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"},"updated":"2023-08-14T20:53:47.000Z","recommendation":"None","cwe":["CWE-918"],"found_by":null,"deleted":null,"id":1092972,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-28155\n- https://github.com/request/request/issues/3442\n- https://github.com/request/request/pull/3444\n- https://doyensec.com/resources/Doyensec_Advisory_RequestSSRF_Q12023.pdf\n- https://security.netapp.com/advisory/ntap-20230413-0007/\n- https://github.com/github/advisory-database/pull/2500\n- https://github.com/cypress-io/request/blob/master/lib/redirect.js#L116\n- https://github.com/request/request/blob/master/lib/redirect.js#L111\n- https://github.com/cypress-io/request/pull/28\n- https://github.com/cypress-io/request/commit/c5bcf21d40fb61feaff21a0e5a2b3934a440024f\n- https://github.com/cypress-io/request/releases/tag/v3.0.0\n- https://github.com/advisories/GHSA-p8p7-x288-28g6","created":"2023-03-16T15:30:19.000Z","reported_by":null,"title":"Server-Side Request Forgery in Request","npm_advisory_id":null,"overview":"The `request` package through 2.88.2 for Node.js and the `@cypress/request` package prior to 3.0.0 allow a bypass of SSRF mitigations via an attacker-controller server that does a cross-protocol redirect (HTTP to HTTPS, or HTTPS to HTTP).\n\nNOTE: The `request` package is no longer supported by the maintainer.","url":"https://github.com/advisories/GHSA-p8p7-x288-28g6"},"1093264":{"findings":[{"version":"7.0.0","paths":["global-agent>semver","@hmcts/nodejs-healthcheck>superagent>semver","applicationinsights>continuation-local-storage>async-listener>semver","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>semver","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>semver"]}],"metadata":null,"vulnerable_versions":">=7.0.0 <7.5.2","module_name":"semver","severity":"moderate","github_advisory_id":"GHSA-c2qf-rxjj-qqgw","cves":["CVE-2022-25883"],"access":"public","patched_versions":">=7.5.2","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-09-01T23:43:55.000Z","recommendation":"Upgrade to version 7.5.2 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1093264,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25883\n- https://github.com/npm/node-semver/pull/564\n- https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441\n- https://security.snyk.io/vuln/SNYK-JS-SEMVER-3247795\n- https://github.com/npm/node-semver/blob/main/classes/range.js#L97-L104\n- https://github.com/npm/node-semver/blob/main/internal/re.js#L138\n- https://github.com/npm/node-semver/blob/main/internal/re.js#L160\n- https://github.com/npm/node-semver/pull/585\n- https://github.com/npm/node-semver/commit/928e56d21150da0413a3333a3148b20e741a920c\n- https://github.com/npm/node-semver/pull/593\n- https://github.com/npm/node-semver/commit/2f8fd41487acf380194579ecb6f8b1bbfe116be0\n- https://github.com/advisories/GHSA-c2qf-rxjj-qqgw","created":"2023-06-21T06:30:28.000Z","reported_by":null,"title":"semver vulnerable to Regular Expression Denial of Service","npm_advisory_id":null,"overview":"Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.","url":"https://github.com/advisories/GHSA-c2qf-rxjj-qqgw"},"1093429":{"findings":[{"version":"2.6.1","paths":["node-fetch","isomorphic-fetch>node-fetch"]}],"metadata":null,"vulnerable_versions":"<2.6.7","module_name":"node-fetch","severity":"high","github_advisory_id":"GHSA-r683-j2x4-v87g","cves":["CVE-2022-0235"],"access":"public","patched_versions":">=2.6.7","cvss":{"score":8.8,"vectorString":"CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-09-07T18:38:42.000Z","recommendation":"Upgrade to version 2.6.7 or later","cwe":["CWE-173","CWE-200","CWE-601"],"found_by":null,"deleted":null,"id":1093429,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-0235\n- https://github.com/node-fetch/node-fetch/commit/36e47e8a6406185921e4985dcbeff140d73eaa10\n- https://huntr.dev/bounties/d26ab655-38d6-48b3-be15-f9ad6b6ae6f7\n- https://github.com/node-fetch/node-fetch/pull/1453\n- https://github.com/node-fetch/node-fetch/commit/5c32f002fdd65b1c6a8f1e3620210813d45c7e60\n- https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00007.html\n- https://github.com/node-fetch/node-fetch/pull/1449/commits/5c32f002fdd65b1c6a8f1e3620210813d45c7e60\n- https://github.com/node-fetch/node-fetch/commit/1ef4b560a17e644a02a3bfdea7631ffeee578b35\n- https://github.com/advisories/GHSA-r683-j2x4-v87g","created":"2022-01-21T23:55:52.000Z","reported_by":null,"title":"node-fetch forwards secure headers to untrusted sites","npm_advisory_id":null,"overview":"node-fetch forwards secure headers such as `authorization`, `www-authenticate`, `cookie`, & `cookie2` when redirecting to a untrusted site.","url":"https://github.com/advisories/GHSA-r683-j2x4-v87g"},"1093500":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.19.3","module_name":"xlsx","severity":"high","github_advisory_id":"GHSA-4r6h-8v6p-xvw6","cves":["CVE-2023-30533"],"access":"public","patched_versions":">=0.19.3","cvss":{"score":7.8,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"},"updated":"2023-09-07T21:28:03.000Z","recommendation":"Upgrade to version 0.19.3 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1093500,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-30533\n- https://cdn.sheetjs.com/advisories/CVE-2023-30533\n- https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/CHANGELOG.md\n- https://git.sheetjs.com/sheetjs/sheetjs/issues/2667\n- https://git.sheetjs.com/sheetjs/sheetjs/issues/2986\n- https://github.com/advisories/GHSA-4r6h-8v6p-xvw6","created":"2023-04-24T09:30:19.000Z","reported_by":null,"title":"Prototype Pollution in sheetJS","npm_advisory_id":null,"overview":"All versions of SheetJS CE through 0.19.2 are vulnerable to \"Prototype Pollution\" when reading specially crafted files. Workflows that do not read arbitrary files (for example, exporting data to spreadsheet files) are unaffected.\n\nA non-vulnerable version cannot be found via npm, as the repository hosted on GitHub and the npm package `xlsx` are no longer maintained.","url":"https://github.com/advisories/GHSA-4r6h-8v6p-xvw6"},"1093639":{"findings":[{"version":"0.4.1","paths":["@hmcts/rpx-xui-node-lib>passport"]}],"metadata":null,"vulnerable_versions":"<0.6.0","module_name":"passport","severity":"moderate","github_advisory_id":"GHSA-v923-w3x8-wh69","cves":["CVE-2022-25896"],"access":"public","patched_versions":">=0.6.0","cvss":{"score":4.8,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L"},"updated":"2023-09-11T16:22:18.000Z","recommendation":"Upgrade to version 0.6.0 or later","cwe":["CWE-384"],"found_by":null,"deleted":null,"id":1093639,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25896\n- https://github.com/jaredhanson/passport/pull/900\n- https://github.com/jaredhanson/passport/commit/7e9b9cf4d7be02428e963fc729496a45baeea608\n- https://snyk.io/vuln/SNYK-JS-PASSPORT-2840631\n- https://github.com/advisories/GHSA-v923-w3x8-wh69","created":"2022-07-02T00:00:19.000Z","reported_by":null,"title":"Passport vulnerable to session regeneration when a users logs in or out","npm_advisory_id":null,"overview":"This affects the package passport before 0.6.0. When a user logs in or logs out, the session is regenerated instead of being closed.","url":"https://github.com/advisories/GHSA-v923-w3x8-wh69"},"1094087":{"findings":[{"version":"0.2.0","paths":["http-proxy-middleware>micromatch>snapdragon>source-map-resolve>decode-uri-component","http-proxy-middleware>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>ts-auto-mock>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>extglob>expand-brackets>snapdragon>source-map-resolve>decode-uri-component"]}],"metadata":null,"vulnerable_versions":"<0.2.1","module_name":"decode-uri-component","severity":"high","github_advisory_id":"GHSA-w573-4hg7-7wgq","cves":["CVE-2022-38900"],"access":"public","patched_versions":">=0.2.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-09-21T22:16:39.000Z","recommendation":"Upgrade to version 0.2.1 or later","cwe":["CWE-20"],"found_by":null,"deleted":null,"id":1094087,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-38900\n- https://github.com/SamVerschueren/decode-uri-component/issues/5\n- https://github.com/sindresorhus/query-string/issues/345\n- https://github.com/SamVerschueren/decode-uri-component/commit/746ca5dcb6667c5d364e782d53c542830e4c10b9\n- https://github.com/SamVerschueren/decode-uri-component/releases/tag/v0.2.1\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ERN6YE3DS7NBW7UH44SCJBMNC2NWQ7SM/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KAC5KQ2SEWAMQ6UZAUBZ5KXKEOESH375/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VNV2GNZXOTEDAJRFH3ZYWRUBGIVL7BSU/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QABOUA2I542UTANVZIVFKWMRYVHLV32D/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UW4SCMT3SEUFVIL7YIADQ5K36GJEO6I5/\n- https://github.com/advisories/GHSA-w573-4hg7-7wgq","created":"2022-11-28T15:30:24.000Z","reported_by":null,"title":"decode-uri-component vulnerable to Denial of Service (DoS)","npm_advisory_id":null,"overview":"decode-uri-component 0.2.0 is vulnerable to Improper Input Validation resulting in DoS.","url":"https://github.com/advisories/GHSA-w573-4hg7-7wgq"},"1094091":{"findings":[{"version":"4.1.0","paths":["@pact-foundation/pact>cli-color>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>yargs>string-width>strip-ansi>ansi-regex","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>wide-align>string-width>strip-ansi>ansi-regex"]}],"metadata":null,"vulnerable_versions":">=4.0.0 <4.1.1","module_name":"ansi-regex","severity":"high","github_advisory_id":"GHSA-93q8-gq69-wqmw","cves":["CVE-2021-3807"],"access":"public","patched_versions":">=4.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-09-21T22:14:52.000Z","recommendation":"Upgrade to version 4.1.1 or later","cwe":["CWE-697","CWE-1333"],"found_by":null,"deleted":null,"id":1094091,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3807\n- https://github.com/chalk/ansi-regex/commit/8d1d7cdb586269882c4bdc1b7325d0c58c8f76f9\n- https://huntr.dev/bounties/5b3cf33b-ede0-4398-9974-800876dfd994\n- https://github.com/chalk/ansi-regex/issues/38#issuecomment-924086311\n- https://app.snyk.io/vuln/SNYK-JS-ANSIREGEX-1583908\n- https://github.com/chalk/ansi-regex/issues/38#issuecomment-925924774\n- https://github.com/chalk/ansi-regex/releases/tag/v6.0.1\n- https://www.oracle.com/security-alerts/cpuapr2022.html\n- https://security.netapp.com/advisory/ntap-20221014-0002/\n- https://github.com/chalk/ansi-regex/commit/419250fa510bf31b4cc672e76537a64f9332e1f1\n- https://github.com/chalk/ansi-regex/commit/75a657da7af875b2e2724fd6331bf0a4b23d3c9a\n- https://github.com/chalk/ansi-regex/commit/c3c0b3f2736b9c01feec0fef33980c43720dcde8\n- https://github.com/advisories/GHSA-93q8-gq69-wqmw","created":"2021-09-20T20:20:09.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in chalk/ansi-regex","npm_advisory_id":null,"overview":"ansi-regex is vulnerable to Inefficient Regular Expression Complexity which could lead to a denial of service when parsing invalid ANSI escape codes.\n\n**Proof of Concept**\n```js\nimport ansiRegex from 'ansi-regex';\nfor(var i = 1; i <= 50000; i++) {\n var time = Date.now();\n var attack_str = \"\\u001B[\"+\";\".repeat(i*10000);\n ansiRegex().test(attack_str)\n var time_cost = Date.now() - time;\n console.log(\"attack_str.length: \" + attack_str.length + \": \" + time_cost+\" ms\")\n}\n```\nThe ReDOS is mainly due to the sub-patterns `[[\\\\]()#;?]*` and `(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]*)*`","url":"https://github.com/advisories/GHSA-93q8-gq69-wqmw"},"1094219":{"findings":[{"version":"4.2.0","paths":["express-session>debug","@hmcts/nodejs-healthcheck>superagent>debug","@hmcts/rpx-xui-node-lib>express>body-parser>debug","@hmcts/rpx-xui-node-lib>express>serve-static>send>debug","@hmcts/rpx-xui-node-lib>ts-auto-mock>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>extglob>expand-brackets>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-proxy-agent>agent-base>debug"]}],"metadata":null,"vulnerable_versions":">=4.0.0 <4.3.1","module_name":"debug","severity":"moderate","github_advisory_id":"GHSA-gxpj-cx7g-858c","cves":["CVE-2017-16137"],"access":"public","patched_versions":">=4.3.1","cvss":{"score":5.3,"vectorString":"CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-10-02T17:59:03.000Z","recommendation":"Upgrade to version 4.3.1 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1094219,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2017-16137\n- https://github.com/visionmedia/debug/issues/501\n- https://github.com/visionmedia/debug/pull/504\n- https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3@%3Ccommits.netbeans.apache.org%3E\n- https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63@%3Cnotifications.netbeans.apache.org%3E\n- https://github.com/debug-js/debug/issues/797\n- https://github.com/debug-js/debug/commit/4e2150207c568adb9ead8f4c4528016081c88020\n- https://github.com/debug-js/debug/commit/71169065b5262f9858ac78cc0b688c84a438f290\n- https://github.com/debug-js/debug/commit/b6d12fdbc63b483e5c969da33ea6adc09946b5ac\n- https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a\n- https://github.com/advisories/GHSA-gxpj-cx7g-858c","created":"2018-08-09T20:18:07.000Z","reported_by":null,"title":"Regular Expression Denial of Service in debug","npm_advisory_id":null,"overview":"Affected versions of `debug` are vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. \n\nAs it takes 50,000 characters to block the event loop for 2 seconds, this issue is a low severity issue.\n\nThis was later re-introduced in version v3.2.0, and then repatched in versions 3.2.7 and 4.3.1.\n\n## Recommendation\n\nVersion 2.x.x: Update to version 2.6.9 or later.\nVersion 3.1.x: Update to version 3.1.0 or later.\nVersion 3.2.x: Update to version 3.2.7 or later.\nVersion 4.x.x: Update to version 4.3.1 or later.","url":"https://github.com/advisories/GHSA-gxpj-cx7g-858c"}},"muted":[],"metadata":{"vulnerabilities":{"info":0,"low":0,"moderate":52,"high":60,"critical":5},"dependencies":836,"devDependencies":7,"optionalDependencies":0,"totalDependencies":843}} +{"actions":[],"advisories":{"1088208":{"findings":[{"version":"0.8.4","paths":["git-rev-sync>shelljs"]}],"metadata":null,"vulnerable_versions":"<0.8.5","module_name":"shelljs","severity":"moderate","github_advisory_id":"GHSA-64g7-mvw6-v9qj","cves":[],"access":"public","patched_versions":">=0.8.5","cvss":{"score":0,"vectorString":null},"updated":"2023-01-11T05:03:39.000Z","recommendation":"Upgrade to version 0.8.5 or later","cwe":["CWE-269"],"found_by":null,"deleted":null,"id":1088208,"references":"- https://github.com/shelljs/shelljs/security/advisories/GHSA-64g7-mvw6-v9qj\n- https://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c/\n- https://github.com/advisories/GHSA-64g7-mvw6-v9qj","created":"2022-01-14T21:09:50.000Z","reported_by":null,"title":"Improper Privilege Management in shelljs","npm_advisory_id":null,"overview":"### Impact\nOutput from the synchronous version of `shell.exec()` may be visible to other users on the same system. You may be affected if you execute `shell.exec()` in multi-user Mac, Linux, or WSL environments, or if you execute `shell.exec()` as the root user.\n\nOther shelljs functions (including the asynchronous version of `shell.exec()`) are not impacted.\n\n### Patches\nPatched in shelljs 0.8.5\n\n### Workarounds\nRecommended action is to upgrade to 0.8.5.\n\n### References\nhttps://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c/\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Ask at https://github.com/shelljs/shelljs/issues/1058\n* Open an issue at https://github.com/shelljs/shelljs/issues/new\n","url":"https://github.com/advisories/GHSA-64g7-mvw6-v9qj"},"1088659":{"findings":[{"version":"2.1.2","paths":["@hmcts/nodejs-healthcheck>superagent>cookiejar"]}],"metadata":null,"vulnerable_versions":"<2.1.4","module_name":"cookiejar","severity":"moderate","github_advisory_id":"GHSA-h452-7996-h45h","cves":["CVE-2022-25901"],"access":"public","patched_versions":">=2.1.4","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-01-23T16:59:53.000Z","recommendation":"Upgrade to version 2.1.4 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1088659,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25901\n- https://github.com/bmeck/node-cookiejar/pull/39\n- https://github.com/bmeck/node-cookiejar/pull/39/commits/eaa00021caf6ae09449dde826108153b578348e5\n- https://security.snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-3176681\n- https://security.snyk.io/vuln/SNYK-JS-COOKIEJAR-3149984\n- https://github.com/bmeck/node-cookiejar/blob/master/cookiejar.js#23L73\n- https://github.com/advisories/GHSA-h452-7996-h45h","created":"2023-01-18T06:31:03.000Z","reported_by":null,"title":"cookiejar Regular Expression Denial of Service via Cookie.parse function","npm_advisory_id":null,"overview":"Versions of the package cookiejar before 2.1.4 are vulnerable to Regular Expression Denial of Service (ReDoS) via the `Cookie.parse` function and other aspects of the API, which use an insecure regular expression for parsing cookie values. Applications could be stalled for extended periods of time if untrusted input is passed to cookie values or attempted to parse from request headers.\n\nProof of concept:\n\n```\nts\\nconst { CookieJar } = require(\"cookiejar\");\n\nconst jar = new CookieJar();\n\nconst start = performance.now();\n\nconst attack = \"a\" + \"t\".repeat(50_000);\njar.setCookie(attack);\n\nconsole.log(`CookieJar.setCookie(): ${performance.now() - start}ms`);\n\n```\n\n```\nCookieJar.setCookie(): 2963.214399999939ms\n```","url":"https://github.com/advisories/GHSA-h452-7996-h45h"},"1088948":{"findings":[{"version":"6.7.1","paths":["@hmcts/rpx-xui-node-lib>openid-client>got"]}],"metadata":null,"vulnerable_versions":"<11.8.5","module_name":"got","severity":"moderate","github_advisory_id":"GHSA-pfrx-2q88-qq97","cves":["CVE-2022-33987"],"access":"public","patched_versions":">=11.8.5","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"},"updated":"2023-01-27T05:05:01.000Z","recommendation":"Upgrade to version 11.8.5 or later","cwe":[],"found_by":null,"deleted":null,"id":1088948,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-33987\n- https://github.com/sindresorhus/got/pull/2047\n- https://github.com/sindresorhus/got/compare/v12.0.3...v12.1.0\n- https://github.com/sindresorhus/got/commit/861ccd9ac2237df762a9e2beed7edd88c60782dc\n- https://github.com/sindresorhus/got/releases/tag/v11.8.5\n- https://github.com/sindresorhus/got/releases/tag/v12.1.0\n- https://github.com/advisories/GHSA-pfrx-2q88-qq97","created":"2022-06-19T00:00:21.000Z","reported_by":null,"title":"Got allows a redirect to a UNIX socket","npm_advisory_id":null,"overview":"The got package before 11.8.5 and 12.1.0 for Node.js allows a redirect to a UNIX socket.","url":"https://github.com/advisories/GHSA-pfrx-2q88-qq97"},"1089067":{"findings":[{"version":"0.2.3","paths":["class-transformer"]}],"metadata":null,"vulnerable_versions":"<0.3.1","module_name":"class-transformer","severity":"moderate","github_advisory_id":"GHSA-6gp3-h3jj-prx4","cves":["CVE-2020-7637"],"access":"public","patched_versions":">=0.3.1","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"},"updated":"2023-01-27T05:08:29.000Z","recommendation":"Upgrade to version 0.3.1 or later","cwe":["CWE-915","CWE-1321"],"found_by":null,"deleted":null,"id":1089067,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-7637\n- https://github.com/typestack/class-transformer/blob/a650d9f490573443f62508bc063b857bcd5e2525/src/ClassTransformer.ts#L29-L31,\n- https://snyk.io/vuln/SNYK-JS-CLASSTRANSFORMER-564431\n- https://github.com/typestack/class-transformer/commit/8f04eb9db02de708f1a20f6f2d2bb309b2fed01e\n- https://github.com/advisories/GHSA-6gp3-h3jj-prx4","created":"2020-04-07T15:47:40.000Z","reported_by":null,"title":"Prototype pollution in class-transformer","npm_advisory_id":null,"overview":"class-transformer through 0.2.3 is vulnerable to Prototype Pollution. The 'classToPlainFromExist' function could be tricked into adding or modifying properties of 'Object.prototype' using a '__proto__' payload.","url":"https://github.com/advisories/GHSA-6gp3-h3jj-prx4"},"1089189":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":"<1.25.0","module_name":"prismjs","severity":"moderate","github_advisory_id":"GHSA-hqhp-5p83-hx96","cves":["CVE-2021-3801"],"access":"public","patched_versions":">=1.25.0","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-01-29T05:02:55.000Z","recommendation":"Upgrade to version 1.25.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089189,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3801\n- https://github.com/prismjs/prism/commit/0ff371bb4775a131634f47d0fe85794c547232f9\n- https://huntr.dev/bounties/8c16ab31-6eb6-46d1-b9a4-387222fe1b8a\n- https://github.com/advisories/GHSA-hqhp-5p83-hx96","created":"2021-09-20T20:44:48.000Z","reported_by":null,"title":"prismjs Regular Expression Denial of Service vulnerability","npm_advisory_id":null,"overview":"Prism is a syntax highlighting library. The prismjs package is vulnerable to ReDoS (regular expression denial of service). An attacker that is able to provide a crafted HTML comment as input may cause an application to consume an excessive amount of CPU.","url":"https://github.com/advisories/GHSA-hqhp-5p83-hx96"},"1089196":{"findings":[{"version":"3.0.2","paths":["redis","@hmcts/rpx-xui-node-lib>redis"]}],"metadata":null,"vulnerable_versions":">=2.6.0 <3.1.1","module_name":"redis","severity":"high","github_advisory_id":"GHSA-35q2-47q7-3pc3","cves":["CVE-2021-29469"],"access":"public","patched_versions":">=3.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-01-29T05:02:31.000Z","recommendation":"Upgrade to version 3.1.1 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089196,"references":"- https://github.com/NodeRedis/node-redis/security/advisories/GHSA-35q2-47q7-3pc3\n- https://nvd.nist.gov/vuln/detail/CVE-2021-29469\n- https://github.com/NodeRedis/node-redis/commit/2d11b6dc9b9774464a91fb4b448bad8bf699629e\n- https://github.com/NodeRedis/node-redis/releases/tag/v3.1.1\n- https://security.netapp.com/advisory/ntap-20210611-0010/\n- https://github.com/advisories/GHSA-35q2-47q7-3pc3","created":"2021-04-27T15:56:03.000Z","reported_by":null,"title":"Node-Redis potential exponential regex in monitor mode","npm_advisory_id":null,"overview":"### Impact\nWhen a client is in monitoring mode, the regex begin used to detected monitor messages could cause exponential backtracking on some strings. This issue could lead to a denial of service.\n\n### Patches\nThe problem was fixed in commit [`2d11b6d`](https://github.com/NodeRedis/node-redis/commit/2d11b6dc9b9774464a91fb4b448bad8bf699629e) and was released in version `3.1.1`.\n\n### References\n#1569 (GHSL-2021-026)","url":"https://github.com/advisories/GHSA-35q2-47q7-3pc3"},"1089270":{"findings":[{"version":"2.7.4","paths":["ejs"]}],"metadata":null,"vulnerable_versions":"<3.1.7","module_name":"ejs","severity":"critical","github_advisory_id":"GHSA-phwq-j96m-2c2q","cves":["CVE-2022-29078"],"access":"public","patched_versions":">=3.1.7","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-01-30T05:02:57.000Z","recommendation":"Upgrade to version 3.1.7 or later","cwe":["CWE-74"],"found_by":null,"deleted":null,"id":1089270,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-29078\n- https://eslam.io/posts/ejs-server-side-template-injection-rce/\n- https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf\n- https://github.com/mde/ejs/releases\n- https://security.netapp.com/advisory/ntap-20220804-0001/\n- https://github.com/advisories/GHSA-phwq-j96m-2c2q","created":"2022-04-26T00:00:40.000Z","reported_by":null,"title":"ejs template injection vulnerability","npm_advisory_id":null,"overview":"The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).","url":"https://github.com/advisories/GHSA-phwq-j96m-2c2q"},"1089434":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<=8.5.1","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-8cf7-32gw-wr33","cves":["CVE-2022-23539"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":0,"vectorString":null},"updated":"2023-01-31T05:01:09.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-327"],"found_by":null,"deleted":null,"id":1089434,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-8cf7-32gw-wr33\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23539\n- https://github.com/advisories/GHSA-8cf7-32gw-wr33","created":"2022-12-22T03:32:22.000Z","reported_by":null,"title":"jsonwebtoken unrestricted key type could lead to legacy keys usage ","npm_advisory_id":null,"overview":"# Overview\n\nVersions `<=8.5.1` of `jsonwebtoken` library could be misconfigured so that legacy, insecure key types are used for signature verification. For example, DSA keys could be used with the RS256 algorithm. \n\n# Am I affected?\n\nYou are affected if you are using an algorithm and a key type other than the combinations mentioned below\n\n| Key type | algorithm |\n|----------|------------------------------------------|\n| ec | ES256, ES384, ES512 |\n| rsa | RS256, RS384, RS512, PS256, PS384, PS512 |\n| rsa-pss | PS256, PS384, PS512 |\n\nAnd for Elliptic Curve algorithms:\n\n| `alg` | Curve |\n|-------|------------|\n| ES256 | prime256v1 |\n| ES384 | secp384r1 |\n| ES512 | secp521r1 |\n\n# How do I fix it?\n\nUpdate to version 9.0.0. This version validates for asymmetric key type and algorithm combinations. Please refer to the above mentioned algorithm / key type combinations for the valid secure configuration. After updating to version 9.0.0, If you still intend to continue with signing or verifying tokens using invalid key type/algorithm value combinations, you’ll need to set the `allowInvalidAsymmetricKeyTypes` option to `true` in the `sign()` and/or `verify()` functions.\n\n# Will the fix impact my users?\n\nThere will be no impact, if you update to version 9.0.0 and you already use a valid secure combination of key type and algorithm. Otherwise, use the `allowInvalidAsymmetricKeyTypes` option to `true` in the `sign()` and `verify()` functions to continue usage of invalid key type/algorithm combination in 9.0.0 for legacy compatibility. \n\n","url":"https://github.com/advisories/GHSA-8cf7-32gw-wr33"},"1089512":{"findings":[{"version":"6.2.1","paths":["log4js"]}],"metadata":null,"vulnerable_versions":"<6.4.0","module_name":"log4js","severity":"moderate","github_advisory_id":"GHSA-82v2-mx6x-wq7q","cves":["CVE-2022-21704"],"access":"public","patched_versions":">=6.4.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"},"updated":"2023-02-01T05:01:40.000Z","recommendation":"Upgrade to version 6.4.0 or later","cwe":["CWE-276"],"found_by":null,"deleted":null,"id":1089512,"references":"- https://github.com/log4js-node/log4js-node/security/advisories/GHSA-82v2-mx6x-wq7q\n- https://github.com/log4js-node/log4js-node/pull/1141/commits/8042252861a1b65adb66931fdf702ead34fa9b76\n- https://github.com/log4js-node/streamroller/pull/87\n- https://github.com/log4js-node/log4js-node/blob/v6.4.0/CHANGELOG.md#640\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21704\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00014.html\n- https://github.com/advisories/GHSA-82v2-mx6x-wq7q","created":"2022-01-21T18:53:27.000Z","reported_by":null,"title":"Incorrect Default Permissions in log4js","npm_advisory_id":null,"overview":"### Impact\r\nDefault file permissions for log files created by the file, fileSync and dateFile appenders are world-readable (in unix). This could cause problems if log files contain sensitive information. This would affect any users that have not supplied their own permissions for the files via the mode parameter in the config.\r\n\r\n### Patches\r\nFixed by:\r\n* https://github.com/log4js-node/log4js-node/pull/1141\r\n* https://github.com/log4js-node/streamroller/pull/87\r\n\r\nReleased to NPM in log4js@6.4.0\r\n\r\n### Workarounds\r\nEvery version of log4js published allows passing the mode parameter to the configuration of file appenders, see the documentation for details.\r\n\r\n### References\r\n\r\nThanks to [ranjit-git](https://www.huntr.dev/users/ranjit-git) for raising the issue, and to @lamweili for fixing the problem.\r\n\r\n### For more information\r\nIf you have any questions or comments about this advisory:\r\n* Open an issue in [logj4s-node](https://github.com/log4js-node/log4js-node)\r\n* Ask a question in the [slack channel](https://join.slack.com/t/log4js-node/shared_invite/enQtODkzMDQ3MzExMDczLWUzZmY0MmI0YWI1ZjFhODY0YjI0YmU1N2U5ZTRkOTYyYzg3MjY5NWI4M2FjZThjYjdiOGM0NjU2NzBmYTJjOGI)\r\n* Email us at [gareth.nomiddlename@gmail.com](mailto:gareth.nomiddlename@gmail.com)\r\n","url":"https://github.com/advisories/GHSA-82v2-mx6x-wq7q"},"1089630":{"findings":[{"version":"1.5.0","paths":["@hmcts/rpx-xui-node-lib>passport-oauth2"]}],"metadata":null,"vulnerable_versions":"<1.6.1","module_name":"passport-oauth2","severity":"moderate","github_advisory_id":"GHSA-f794-r6xc-hf3v","cves":["CVE-2021-41580"],"access":"public","patched_versions":">=1.6.1","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"},"updated":"2023-02-01T05:06:26.000Z","recommendation":"Upgrade to version 1.6.1 or later","cwe":["CWE-287"],"found_by":null,"deleted":null,"id":1089630,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-41580\n- https://github.com/jaredhanson/passport-oauth2/pull/144\n- https://github.com/jaredhanson/passport-oauth2/commit/8e3bcdff145a2219033bd782fc517229fe3e05ea\n- https://github.com/jaredhanson/passport-oauth2/compare/v1.6.0...v1.6.1\n- https://medium.com/passportjs/no-access-token-no-service-7fb017c9e262\n- https://github.com/advisories/GHSA-f794-r6xc-hf3v","created":"2021-09-29T17:18:32.000Z","reported_by":null,"title":"Improper Access Control in passport-oauth2","npm_advisory_id":null,"overview":"The passport-oauth2 package before 1.6.1 for Node.js mishandles the error condition of failure to obtain an access token. This is exploitable in certain use cases where an OAuth identity provider uses an HTTP 200 status code for authentication-failure error reports, and an application grants authorization upon simply receiving the access token (i.e., does not try to use the token). NOTE: the passport-oauth2 vendor does not consider this a passport-oauth2 vulnerability.","url":"https://github.com/advisories/GHSA-f794-r6xc-hf3v"},"1089682":{"findings":[{"version":"6.0.5","paths":["@pact-foundation/pact-node>tar","@pact-foundation/pact>@pact-foundation/pact-node>tar","playwright>fsevents>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar"]}],"metadata":null,"vulnerable_versions":">=6.0.0 <6.1.1","module_name":"tar","severity":"high","github_advisory_id":"GHSA-3jfq-g458-7qm9","cves":["CVE-2021-32804"],"access":"public","patched_versions":">=6.1.1","cvss":{"score":8.2,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"},"updated":"2023-02-01T05:05:55.000Z","recommendation":"Upgrade to version 6.1.1 or later","cwe":["CWE-22"],"found_by":null,"deleted":null,"id":1089682,"references":"- https://github.com/npm/node-tar/security/advisories/GHSA-3jfq-g458-7qm9\n- https://github.com/npm/node-tar/commit/1f036ca23f64a547bdd6c79c1a44bc62e8115da4\n- https://www.npmjs.com/advisories/1770\n- https://www.npmjs.com/package/tar\n- https://nvd.nist.gov/vuln/detail/CVE-2021-32804\n- https://www.oracle.com/security-alerts/cpuoct2021.html\n- https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf\n- https://github.com/advisories/GHSA-3jfq-g458-7qm9","created":"2021-08-03T19:06:36.000Z","reported_by":null,"title":"Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization","npm_advisory_id":null,"overview":"### Impact\n\nArbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution\n\n`node-tar` aims to prevent extraction of absolute file paths by turning absolute paths into relative paths when the `preservePaths` flag is not set to `true`. This is achieved by stripping the absolute path root from any absolute file paths contained in a tar file. For example `/home/user/.bashrc` would turn into `home/user/.bashrc`. \n\nThis logic was insufficient when file paths contained repeated path roots such as `////home/user/.bashrc`. `node-tar` would only strip a single path root from such paths. When given an absolute file path with repeating path roots, the resulting path (e.g. `///home/user/.bashrc`) would still resolve to an absolute path, thus allowing arbitrary file creation and overwrite. \n\n### Patches\n\n3.2.2 || 4.4.14 || 5.0.6 || 6.1.1\n\nNOTE: an adjacent issue [CVE-2021-32803](https://github.com/npm/node-tar/security/advisories/GHSA-r628-mhmh-qjhw) affects this release level. Please ensure you update to the latest patch levels that address CVE-2021-32803 as well if this adjacent issue affects your `node-tar` use case.\n\n### Workarounds\n\nUsers may work around this vulnerability without upgrading by creating a custom `onentry` method which sanitizes the `entry.path` or a `filter` method which removes entries with absolute paths.\n\n```js\nconst path = require('path')\nconst tar = require('tar')\n\ntar.x({\n file: 'archive.tgz',\n // either add this function...\n onentry: (entry) => {\n if (path.isAbsolute(entry.path)) {\n entry.path = sanitizeAbsolutePathSomehow(entry.path)\n entry.absolute = path.resolve(entry.path)\n }\n },\n\n // or this one\n filter: (file, entry) => {\n if (path.isAbsolute(entry.path)) {\n return false\n } else {\n return true\n }\n }\n})\n```\n\nUsers are encouraged to upgrade to the latest patch versions, rather than attempt to sanitize tar input themselves.","url":"https://github.com/advisories/GHSA-3jfq-g458-7qm9"},"1089698":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-g973-978j-2c3p","cves":["CVE-2021-32014"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:05:54.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-345","CWE-400"],"found_by":null,"deleted":null,"id":1089698,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32014\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-g973-978j-2c3p","created":"2021-07-22T19:47:15.000Z","reported_by":null,"title":"Denial of Service in SheetJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (CPU consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js.","url":"https://github.com/advisories/GHSA-g973-978j-2c3p"},"1089699":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-3x9f-74h4-2fqr","cves":["CVE-2021-32012"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:06:10.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089699,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32012\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-3x9f-74h4-2fqr","created":"2021-07-22T19:48:17.000Z","reported_by":null,"title":"Denial of Service in SheetJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (memory consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js (issue 1 of 2).","url":"https://github.com/advisories/GHSA-3x9f-74h4-2fqr"},"1089700":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.17.0","module_name":"xlsx","severity":"moderate","github_advisory_id":"GHSA-8vcr-vxm8-293m","cves":["CVE-2021-32013"],"access":"public","patched_versions":">=0.17.0","cvss":{"score":5.5,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"},"updated":"2023-02-01T05:06:00.000Z","recommendation":"Upgrade to version 0.17.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089700,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-32013\n- https://floqast.com/engineering-blog/post/fuzzing-and-parsing-securely/\n- https://sheetjs.com/pro\n- https://www.npmjs.com/package/xlsx/v/0.17.0\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-8vcr-vxm8-293m","created":"2021-07-22T19:48:13.000Z","reported_by":null,"title":"Denial of Service in SheetsJS Pro","npm_advisory_id":null,"overview":"SheetJS Pro through 0.16.9 allows attackers to cause a denial of service (memory consumption) via a crafted .xlsx document that is mishandled when read by xlsx.js (issue 2 of 2).","url":"https://github.com/advisories/GHSA-8vcr-vxm8-293m"},"1089716":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":"<1.24.0","module_name":"prismjs","severity":"high","github_advisory_id":"GHSA-gj77-59wh-66hg","cves":["CVE-2021-32723"],"access":"public","patched_versions":">=1.24.0","cvss":{"score":7.4,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H"},"updated":"2023-02-01T05:05:49.000Z","recommendation":"Upgrade to version 1.24.0 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1089716,"references":"- https://github.com/PrismJS/prism/security/advisories/GHSA-gj77-59wh-66hg\n- https://github.com/PrismJS/prism/pull/2688\n- https://github.com/PrismJS/prism/pull/2774\n- https://github.com/PrismJS/prism/commit/d85e30da6755fdbe7f8559f8e75d122297167018\n- https://nvd.nist.gov/vuln/detail/CVE-2021-32723\n- https://www.oracle.com/security-alerts/cpujan2022.html\n- https://github.com/advisories/GHSA-gj77-59wh-66hg","created":"2021-06-28T18:33:18.000Z","reported_by":null,"title":"Regular Expression Denial of Service (ReDoS) in Prism","npm_advisory_id":null,"overview":"Some languages before 1.24.0 are vulnerable to Regular Expression Denial of Service (ReDoS).\n\n### Impact\n\nWhen Prism is used to highlight untrusted (user-given) text, an attacker can craft a string that will take a very very long time to highlight. Do not use the following languages to highlight untrusted text.\n\n- ASCIIDoc\n- ERB\n\nOther languages are __not__ affected and can be used to highlight untrusted text.\n\n### Patches\nThis problem has been fixed in Prism v1.24.\n\n### References\n\n- PrismJS/prism#2774\n- PrismJS/prism#2688\n","url":"https://github.com/advisories/GHSA-gj77-59wh-66hg"},"1090424":{"findings":[{"version":"1.23.0","paths":["ngx-md>prismjs"]}],"metadata":null,"vulnerable_versions":">=1.14.0 <1.27.0","module_name":"prismjs","severity":"high","github_advisory_id":"GHSA-3949-f494-cm99","cves":["CVE-2022-23647"],"access":"public","patched_versions":">=1.27.0","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:L"},"updated":"2023-02-03T05:06:26.000Z","recommendation":"Upgrade to version 1.27.0 or later","cwe":["CWE-79"],"found_by":null,"deleted":null,"id":1090424,"references":"- https://github.com/PrismJS/prism/security/advisories/GHSA-3949-f494-cm99\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23647\n- https://github.com/PrismJS/prism/pull/3341\n- https://github.com/PrismJS/prism/commit/e002e78c343154e1c0ddf9d6a0bb85689e1a5c7c\n- https://github.com/advisories/GHSA-3949-f494-cm99","created":"2022-02-22T19:32:18.000Z","reported_by":null,"title":"Cross-site Scripting in Prism","npm_advisory_id":null,"overview":"### Impact\nPrism's [Command line plugin](https://prismjs.com/plugins/command-line/) can be used by attackers to achieve an XSS attack. The Command line plugin did not properly escape its output, leading to the input text being inserted into the DOM as HTML code.\n\nServer-side usage of Prism is not impacted. Websites that do not use the Command Line plugin are also not impacted.\n\n### Patches\nThis bug has been fixed in v1.27.0.\n\n### Workarounds\nDo not use the Command line plugin on untrusted inputs, or sanitized all code blocks (remove all HTML code text) from all code blocks that use the Command line plugin.\n\n### References\n- https://github.com/PrismJS/prism/pull/3341","url":"https://github.com/advisories/GHSA-3949-f494-cm99"},"1091087":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<=8.5.1","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-hjrf-2m68-5959","cves":["CVE-2022-23541"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L"},"updated":"2023-01-29T05:06:34.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-287"],"found_by":null,"deleted":null,"id":1091087,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-hjrf-2m68-5959\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23541\n- https://github.com/auth0/node-jsonwebtoken/releases/tag/v9.0.0\n- https://github.com/advisories/GHSA-hjrf-2m68-5959","created":"2022-12-22T03:33:19.000Z","reported_by":null,"title":"jsonwebtoken's insecure implementation of key retrieval function could lead to Forgeable Public/Private Tokens from RSA to HMAC","npm_advisory_id":null,"overview":"# Overview\n\nVersions `<=8.5.1` of `jsonwebtoken` library can be misconfigured so that passing a poorly implemented key retrieval function (referring to the `secretOrPublicKey` argument from the [readme link](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback)) will result in incorrect verification of tokens. There is a possibility of using a different algorithm and key combination in verification than the one that was used to sign the tokens. Specifically, tokens signed with an asymmetric public key could be verified with a symmetric HS256 algorithm. This can lead to successful validation of forged tokens. \n\n# Am I affected?\n\nYou will be affected if your application is supporting usage of both symmetric key and asymmetric key in jwt.verify() implementation with the same key retrieval function. \n\n# How do I fix it?\n \nUpdate to version 9.0.0.\n\n# Will the fix impact my users?\n\nThere is no impact for end users","url":"https://github.com/advisories/GHSA-hjrf-2m68-5959"},"1091311":{"findings":[{"version":"6.0.5","paths":["@pact-foundation/pact-node>tar","@pact-foundation/pact>@pact-foundation/pact-node>tar","playwright>fsevents>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar"]}],"metadata":null,"vulnerable_versions":">=6.0.0 <6.1.2","module_name":"tar","severity":"high","github_advisory_id":"GHSA-r628-mhmh-qjhw","cves":["CVE-2021-32803"],"access":"public","patched_versions":">=6.1.2","cvss":{"score":8.2,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"},"updated":"2023-03-09T16:44:58.000Z","recommendation":"Upgrade to version 6.1.2 or later","cwe":["CWE-22","CWE-23","CWE-59"],"found_by":null,"deleted":null,"id":1091311,"references":"- https://github.com/npm/node-tar/security/advisories/GHSA-r628-mhmh-qjhw\n- https://github.com/npm/node-tar/commit/9dbdeb6df8e9dbd96fa9e84341b9d74734be6c20\n- https://www.npmjs.com/advisories/1771\n- https://nvd.nist.gov/vuln/detail/CVE-2021-32803\n- https://www.npmjs.com/package/tar\n- https://www.oracle.com/security-alerts/cpuoct2021.html\n- https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf\n- https://github.com/isaacs/node-tar/commit/46fe35083e2676e31c4e0a81639dce6da7aaa356\n- https://github.com/isaacs/node-tar/commit/5987d9a41f6bfbf1ddab1098e1fdcf1a5618f571\n- https://github.com/isaacs/node-tar/commit/85d3a942b4064e4ff171f91696fced7975167349\n- https://github.com/isaacs/node-tar/commit/9dbdeb6df8e9dbd96fa9e84341b9d74734be6c20\n- https://github.com/advisories/GHSA-r628-mhmh-qjhw","created":"2021-08-03T19:00:40.000Z","reported_by":null,"title":"Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning","npm_advisory_id":null,"overview":"### Impact\n\nArbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution\n\n`node-tar` aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary `stat` calls to determine whether a given path is a directory, paths are cached when directories are created.\n\nThis logic was insufficient when extracting tar files that contained both a directory and a symlink with the same name as the directory. This order of operations resulted in the directory being created and added to the `node-tar` directory cache. When a directory is present in the directory cache, subsequent calls to mkdir for that directory are skipped. However, this is also where `node-tar` checks for symlinks occur.\n\nBy first creating a directory, and then replacing that directory with a symlink, it was thus possible to bypass `node-tar` symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.\n\nThis issue was addressed in releases 3.2.3, 4.4.15, 5.0.7 and 6.1.2.\n\n### Patches\n\n3.2.3 || 4.4.15 || 5.0.7 || 6.1.2\n\n### Workarounds\n\nUsers may work around this vulnerability without upgrading by creating a custom `filter` method which prevents the extraction of symbolic links.\n\n```js\nconst tar = require('tar')\n\ntar.x({\n file: 'archive.tgz',\n filter: (file, entry) => {\n if (entry.type === 'SymbolicLink') {\n return false\n } else {\n return true\n }\n }\n})\n```\n\nUsers are encouraged to upgrade to the latest patch versions, rather than attempt to sanitize tar input themselves.","url":"https://github.com/advisories/GHSA-r628-mhmh-qjhw"},"1091341":{"findings":[{"version":"6.0.5","paths":["@pact-foundation/pact-node>tar","@pact-foundation/pact>@pact-foundation/pact-node>tar","playwright>fsevents>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar"]}],"metadata":null,"vulnerable_versions":">=6.0.0 <6.1.7","module_name":"tar","severity":"high","github_advisory_id":"GHSA-9r2w-394v-53qc","cves":["CVE-2021-37701"],"access":"public","patched_versions":">=6.1.7","cvss":{"score":8.2,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"},"updated":"2023-03-13T21:51:00.000Z","recommendation":"Upgrade to version 6.1.7 or later","cwe":["CWE-22","CWE-59"],"found_by":null,"deleted":null,"id":1091341,"references":"- https://github.com/npm/node-tar/security/advisories/GHSA-9r2w-394v-53qc\n- https://www.npmjs.com/package/tar\n- https://nvd.nist.gov/vuln/detail/CVE-2021-37701\n- https://www.oracle.com/security-alerts/cpuoct2021.html\n- https://www.debian.org/security/2021/dsa-5008\n- https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00023.html\n- https://github.com/advisories/GHSA-9r2w-394v-53qc","created":"2021-08-31T16:05:27.000Z","reported_by":null,"title":"Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning using symbolic links","npm_advisory_id":null,"overview":"### Impact\n\nArbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution\n\n`node-tar` aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary stat calls to determine whether a given path is a directory, paths are cached when directories are created.\n\nThis logic was insufficient when extracting tar files that contained both a directory and a symlink with the same name as the directory, where the symlink and directory names in the archive entry used backslashes as a path separator on posix systems. The cache checking logic used both `\\` and `/` characters as path separators, however `\\` is a valid filename character on posix systems.\n\nBy first creating a directory, and then replacing that directory with a symlink, it was thus possible to bypass node-tar symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.\n\nAdditionally, a similar confusion could arise on case-insensitive filesystems. If a tar archive contained a directory at `FOO`, followed by a symbolic link named `foo`, then on case-insensitive file systems, the creation of the symbolic link would remove the directory from the filesystem, but _not_ from the internal directory cache, as it would not be treated as a cache hit. A subsequent file entry within the `FOO` directory would then be placed in the target of the symbolic link, thinking that the directory had already been created. \n\nThese issues were addressed in releases 4.4.16, 5.0.8 and 6.1.7.\n\nThe v3 branch of `node-tar` has been deprecated and did not receive patches for these issues. If you are still using a v3 release we recommend you update to a more recent version of `node-tar`. If this is not possible, a workaround is available below.\n\n### Patches\n\n4.4.16 || 5.0.8 || 6.1.7\n\n### Workarounds\n\nUsers may work around this vulnerability without upgrading by creating a custom filter method which prevents the extraction of symbolic links.\n\n```js\nconst tar = require('tar')\n\ntar.x({\n file: 'archive.tgz',\n filter: (file, entry) => {\n if (entry.type === 'SymbolicLink') {\n return false\n } else {\n return true\n }\n }\n})\n```\n\nUsers are encouraged to upgrade to the latest patched versions, rather than attempt to sanitize tar input themselves.\n\n### Fix\n\nThe problem is addressed in the following ways:\n\n1. All paths are normalized to use `/` as a path separator, replacing `\\` with `/` on Windows systems, and leaving `\\` intact in the path on posix systems. This is performed in depth, at every level of the program where paths are consumed.\n2. Directory cache pruning is performed case-insensitively. This _may_ result in undue cache misses on case-sensitive file systems, but the performance impact is negligible.\n\n#### Caveat\n\nNote that this means that the `entry` objects exposed in various parts of tar's API will now always use `/` as a path separator, even on Windows systems. This is not expected to cause problems, as `/` is a valid path separator on Windows systems, but _may_ result in issues if `entry.path` is compared against a path string coming from some other API such as `fs.realpath()` or `path.resolve()`.\n\nUsers are encouraged to always normalize paths using a well-tested method such as `path.resolve()` before comparing paths to one another.","url":"https://github.com/advisories/GHSA-9r2w-394v-53qc"},"1091344":{"findings":[{"version":"6.0.5","paths":["@pact-foundation/pact-node>tar","@pact-foundation/pact>@pact-foundation/pact-node>tar","playwright>fsevents>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar"]}],"metadata":null,"vulnerable_versions":">=6.0.0 <6.1.9","module_name":"tar","severity":"high","github_advisory_id":"GHSA-5955-9wpr-37jh","cves":["CVE-2021-37713"],"access":"public","patched_versions":">=6.1.9","cvss":{"score":8.2,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"},"updated":"2023-03-13T21:47:38.000Z","recommendation":"Upgrade to version 6.1.9 or later","cwe":["CWE-22"],"found_by":null,"deleted":null,"id":1091344,"references":"- https://github.com/npm/node-tar/security/advisories/GHSA-5955-9wpr-37jh\n- https://www.npmjs.com/package/tar\n- https://nvd.nist.gov/vuln/detail/CVE-2021-37713\n- https://www.oracle.com/security-alerts/cpuoct2021.html\n- https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf\n- https://github.com/isaacs/node-tar/commit/52b09e309bcae0c741a7eb79a17ef36e7828b946\n- https://github.com/isaacs/node-tar/commit/82eac952f7c10765969ed464e549375854b26edc\n- https://github.com/isaacs/node-tar/commit/875a37e3ec031186fc6599f6807341f56c584598\n- https://github.com/advisories/GHSA-5955-9wpr-37jh","created":"2021-08-31T16:05:05.000Z","reported_by":null,"title":"Arbitrary File Creation/Overwrite on Windows via insufficient relative path sanitization","npm_advisory_id":null,"overview":"### Impact\n\nArbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution\n\nnode-tar aims to guarantee that any file whose location would be outside of the extraction target directory is not extracted. This is, in part, accomplished by sanitizing absolute paths of entries within the archive, skipping archive entries that contain `..` path portions, and resolving the sanitized paths against the extraction target directory.\n\nThis logic was insufficient on Windows systems when extracting tar files that contained a path that was not an absolute path, but specified a drive letter different from the extraction target, such as `C:some\\path`. If the drive letter does not match the extraction target, for example `D:\\extraction\\dir`, then the result of `path.resolve(extractionDirectory, entryPath)` would resolve against the current working directory on the `C:` drive, rather than the extraction target directory.\n\nAdditionally, a `..` portion of the path could occur immediately after the drive letter, such as `C:../foo`, and was not properly sanitized by the logic that checked for `..` within the normalized and split portions of the path.\n\nThis only affects users of `node-tar` on Windows systems.\n\n### Patches\n\n4.4.18 || 5.0.10 || 6.1.9\n\n### Workarounds\n\nThere is no reasonable way to work around this issue without performing the same path normalization procedures that node-tar now does.\n\nUsers are encouraged to upgrade to the latest patched versions of node-tar, rather than attempt to sanitize paths themselves.\n\n### Fix\n\nThe fixed versions strip path roots from all paths prior to being resolved against the extraction target folder, even if such paths are not \"absolute\".\n\nAdditionally, a path starting with a drive letter and then two dots, like `c:../`, would bypass the check for `..` path portions. This is checked properly in the patched versions.\n\nFinally, a defense in depth check is added, such that if the `entry.absolute` is outside of the extraction taret, and we are not in preservePaths:true mode, a warning is raised on that entry, and it is skipped. Currently, it is believed that this check is redundant, but it did catch some oversights in development.\n","url":"https://github.com/advisories/GHSA-5955-9wpr-37jh"},"1091430":{"findings":[{"version":"2.29.0","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>moment","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>moment"]}],"metadata":null,"vulnerable_versions":"<2.29.2","module_name":"moment","severity":"high","github_advisory_id":"GHSA-8hfj-j24r-96c4","cves":["CVE-2022-24785"],"access":"public","patched_versions":">=2.29.2","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"},"updated":"2023-03-20T22:35:12.000Z","recommendation":"Upgrade to version 2.29.2 or later","cwe":["CWE-22","CWE-27"],"found_by":null,"deleted":null,"id":1091430,"references":"- https://github.com/moment/moment/security/advisories/GHSA-8hfj-j24r-96c4\n- https://nvd.nist.gov/vuln/detail/CVE-2022-24785\n- https://github.com/moment/moment/commit/4211bfc8f15746be4019bba557e29a7ba83d54c5\n- https://www.tenable.com/security/tns-2022-09\n- https://security.netapp.com/advisory/ntap-20220513-0006/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6QIO6YNLTK2T7SPKDS4JEL45FANLNC2Q/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ORJX2LF6KMPIHP6B2P6KZIVKMLE3LVJ5/\n- https://lists.debian.org/debian-lts-announce/2023/01/msg00035.html\n- https://github.com/advisories/GHSA-8hfj-j24r-96c4","created":"2022-04-04T21:25:48.000Z","reported_by":null,"title":"Path Traversal: 'dir/../../filename' in moment.locale","npm_advisory_id":null,"overview":"### Impact\nThis vulnerability impacts npm (server) users of moment.js, especially if user provided locale string, eg `fr` is directly used to switch moment locale.\n\n### Patches\nThis problem is patched in 2.29.2, and the patch can be applied to all affected versions (from 1.0.1 up until 2.29.1, inclusive).\n\n### Workarounds\nSanitize user-provided locale name before passing it to moment.js.\n\n### References\n_Are there any links users can visit to find out more?_\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [moment repo](https://github.com/moment/moment)\n","url":"https://github.com/advisories/GHSA-8hfj-j24r-96c4"},"1091441":{"findings":[{"version":"2.29.0","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>moment","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>moment"]}],"metadata":null,"vulnerable_versions":">=2.18.0 <2.29.4","module_name":"moment","severity":"high","github_advisory_id":"GHSA-wc69-rhjr-hc9g","cves":["CVE-2022-31129"],"access":"public","patched_versions":">=2.29.4","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-03-20T23:29:32.000Z","recommendation":"Upgrade to version 2.29.4 or later","cwe":["CWE-400","CWE-1333"],"found_by":null,"deleted":null,"id":1091441,"references":"- https://github.com/moment/moment/security/advisories/GHSA-wc69-rhjr-hc9g\n- https://github.com/moment/moment/pull/6015#issuecomment-1152961973\n- https://github.com/moment/moment/commit/9a3b5894f3d5d602948ac8a02e4ee528a49ca3a3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-31129\n- https://huntr.dev/bounties/f0952b67-f2ff-44a9-a9cd-99e0a87cb633/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6QIO6YNLTK2T7SPKDS4JEL45FANLNC2Q/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ORJX2LF6KMPIHP6B2P6KZIVKMLE3LVJ5/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/IWY24RJA3SBJGA5N4CU4VBPHJPPPJL5O/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZMX5YHELQVCGKKQVFXIYOTBMN23YYSRO/\n- https://security.netapp.com/advisory/ntap-20221014-0003/\n- https://lists.debian.org/debian-lts-announce/2023/01/msg00035.html\n- https://github.com/moment/moment/pull/6015/commits/4bbb9f3ccbe231de40207503f344fe5ce97584f4\n- https://github.com/moment/moment/pull/6015/commits/bfd4f2375d5c1a2106246721d693a9611dddfbfe\n- https://github.com/moment/moment/pull/6015/commits/dc0d180e90d8a84f7ff13572363330a22b3ea504\n- https://github.com/advisories/GHSA-wc69-rhjr-hc9g","created":"2022-07-06T18:38:49.000Z","reported_by":null,"title":"Moment.js vulnerable to Inefficient Regular Expression Complexity","npm_advisory_id":null,"overview":"### Impact\n\n* using string-to-date parsing in moment (more specifically rfc2822 parsing, which is tried by default) has quadratic (N^2) complexity on specific inputs\n* noticeable slowdown is observed with inputs above 10k characters\n* users who pass user-provided strings without sanity length checks to moment constructor are vulnerable to (Re)DoS attacks\n\n### Patches\nThe problem is patched in 2.29.4, the patch can be applied to all affected versions with minimal tweaking.\n\n### Workarounds\nIn general, given the proliferation of ReDoS attacks, it makes sense to limit the length of the user input to something sane, like 200 characters or less. I haven't seen legitimate cases of date-time strings longer than that, so all moment users who do pass a user-originating string to constructor are encouraged to apply such a rudimentary filter, that would help with this but also most future ReDoS vulnerabilities.\n\n### References\nThere is an excellent writeup of the issue here: https://github.com/moment/moment/pull/6015#issuecomment-1152961973=\n\n### Details\nThe issue is rooted in the code that removes legacy comments (stuff inside parenthesis) from strings during rfc2822 parsing. `moment(\"(\".repeat(500000))` will take a few minutes to process, which is unacceptable.","url":"https://github.com/advisories/GHSA-wc69-rhjr-hc9g"},"1091453":{"findings":[{"version":"0.8.4","paths":["git-rev-sync>shelljs"]}],"metadata":null,"vulnerable_versions":"<0.8.5","module_name":"shelljs","severity":"high","github_advisory_id":"GHSA-4rq4-32rv-6wp6","cves":["CVE-2022-0144"],"access":"public","patched_versions":">=0.8.5","cvss":{"score":7.1,"vectorString":"CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H"},"updated":"2023-03-21T20:10:17.000Z","recommendation":"Upgrade to version 0.8.5 or later","cwe":["CWE-269"],"found_by":null,"deleted":null,"id":1091453,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-0144\n- https://github.com/shelljs/shelljs/commit/d919d22dd6de385edaa9d90313075a77f74b338c\n- https://huntr.dev/bounties/50996581-c08e-4eed-a90e-c0bac082679c\n- https://github.com/advisories/GHSA-4rq4-32rv-6wp6","created":"2022-01-21T23:37:28.000Z","reported_by":null,"title":"Improper Privilege Management in shelljs","npm_advisory_id":null,"overview":"shelljs is vulnerable to Improper Privilege Management","url":"https://github.com/advisories/GHSA-4rq4-32rv-6wp6"},"1091470":{"findings":[{"version":"1.8.3","paths":["@pact-foundation/pact-node>underscore","@pact-foundation/pact>@pact-foundation/pact-node>underscore"]}],"metadata":null,"vulnerable_versions":">=1.3.2 <1.12.1","module_name":"underscore","severity":"critical","github_advisory_id":"GHSA-cf4h-3jhx-xvhq","cves":["CVE-2021-23358"],"access":"public","patched_versions":">=1.12.1","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-03-23T20:56:35.000Z","recommendation":"Upgrade to version 1.12.1 or later","cwe":["CWE-94"],"found_by":null,"deleted":null,"id":1091470,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-23358\n- https://github.com/jashkenas/underscore/pull/2917\n- https://github.com/jashkenas/underscore/commit/4c73526d43838ad6ab43a6134728776632adeb66\n- https://github.com/jashkenas/underscore/releases/tag/1.12.1\n- https://snyk.io/vuln/SNYK-JS-UNDERSCORE-1080984\n- https://www.npmjs.com/package/underscore\n- https://github.com/jashkenas/underscore/blob/master/modules/template.js%23L71\n- https://lists.debian.org/debian-lts-announce/2021/03/msg00038.html\n- https://www.debian.org/security/2021/dsa-4883\n- https://lists.apache.org/thread.html/r5df90c46f7000c4aab246e947f62361ecfb849c5a553dcdb0ef545e1@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/r770f910653772317b117ab4472b0a32c266ee4abbafda28b8a6f9306@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/raae088abdfa4fbd84e1d19d7a7ffe52bf8e426b83e6599ea9a734dba@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/rbc84926bacd377503a3f5c37b923c1931f9d343754488d94e6f08039@%3Cissues.cordova.apache.org%3E\n- https://lists.apache.org/thread.html/re69ee408b3983b43e9c4a82a9a17cbbf8681bb91a4b61b46f365aeaf@%3Cissues.cordova.apache.org%3E\n- https://www.tenable.com/security/tns-2021-14\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EOKATXXETD2PF3OR36Q5PD2VSVAR6J5Z/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FGEE7U4Z655A2MK5EW4UQQZ7B64XJWBV/\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1081504\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBJASHKENAS-1081505\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1081503\n- https://github.com/advisories/GHSA-cf4h-3jhx-xvhq","created":"2021-05-06T16:09:43.000Z","reported_by":null,"title":"Arbitrary Code Execution in underscore","npm_advisory_id":null,"overview":"The package `underscore` from 1.13.0-0 and before 1.13.0-2, from 1.3.2 and before 1.12.1 are vulnerable to Arbitrary Code Execution via the template function, particularly when a variable property is passed as an argument as it is not sanitized.","url":"https://github.com/advisories/GHSA-cf4h-3jhx-xvhq"},"1091472":{"findings":[{"version":"0.2.3","paths":["@pact-foundation/pact-node>request>http-signature>jsprim>json-schema","@pact-foundation/pact>@pact-foundation/pact-node>request>http-signature>jsprim>json-schema"]}],"metadata":null,"vulnerable_versions":"<0.4.0","module_name":"json-schema","severity":"critical","github_advisory_id":"GHSA-896r-f27r-55mw","cves":["CVE-2021-3918"],"access":"public","patched_versions":">=0.4.0","cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-03-23T20:35:18.000Z","recommendation":"Upgrade to version 0.4.0 or later","cwe":["CWE-915","CWE-1321"],"found_by":null,"deleted":null,"id":1091472,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3918\n- https://github.com/kriszyp/json-schema/commit/22f146111f541d9737e832823699ad3528ca7741\n- https://huntr.dev/bounties/bb6ccd63-f505-4e3a-b55f-cd2662c261a9\n- https://github.com/kriszyp/json-schema/commit/b62f1da1ff5442f23443d6be6a92d00e65cba93a\n- https://github.com/kriszyp/json-schema/commit/f6f6a3b02d667aa4ba2d5d50cc19208c4462abfa\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00013.html\n- https://github.com/advisories/GHSA-896r-f27r-55mw","created":"2021-11-19T20:16:17.000Z","reported_by":null,"title":"json-schema is vulnerable to Prototype Pollution","npm_advisory_id":null,"overview":"json-schema before version 0.4.0 is vulnerable to Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').","url":"https://github.com/advisories/GHSA-896r-f27r-55mw"},"1092316":{"findings":[{"version":"4.1.0","paths":["playwright>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>make-fetch-happen>http-cache-semantics","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>make-fetch-happen>http-cache-semantics","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-cache-semantics"]}],"metadata":null,"vulnerable_versions":"<4.1.1","module_name":"http-cache-semantics","severity":"high","github_advisory_id":"GHSA-rc47-6667-2j5j","cves":["CVE-2022-25881"],"access":"public","patched_versions":">=4.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-06-22T17:26:15.000Z","recommendation":"Upgrade to version 4.1.1 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1092316,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25881\n- https://github.com/kornelski/http-cache-semantics/blob/master/index.js%23L83\n- https://security.snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-3253332\n- https://security.snyk.io/vuln/SNYK-JS-HTTPCACHESEMANTICS-3248783\n- https://github.com/kornelski/http-cache-semantics/commit/560b2d8ef452bbba20ffed69dc155d63ac757b74\n- https://security.netapp.com/advisory/ntap-20230622-0008/\n- https://github.com/advisories/GHSA-rc47-6667-2j5j","created":"2023-01-31T06:30:26.000Z","reported_by":null,"title":"http-cache-semantics vulnerable to Regular Expression Denial of Service","npm_advisory_id":null,"overview":"http-cache semantics contains an Inefficient Regular Expression Complexity , leading to Denial of Service. This affects versions of the package http-cache-semantics before 4.1.1. The issue can be exploited via malicious request header values sent to a server, when that server reads the cache policy from the request using this library.","url":"https://github.com/advisories/GHSA-rc47-6667-2j5j"},"1092423":{"findings":[{"version":"2.6.3","paths":["@hmcts/nodejs-healthcheck>@hmcts/nodejs-logging>winston>async","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>winston>async"]}],"metadata":null,"vulnerable_versions":">=2.0.0 <2.6.4","module_name":"async","severity":"high","github_advisory_id":"GHSA-fwr7-v2mv-hh25","cves":["CVE-2021-43138"],"access":"public","patched_versions":">=2.6.4","cvss":{"score":7.8,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"},"updated":"2023-07-07T18:19:47.000Z","recommendation":"Upgrade to version 2.6.4 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1092423,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-43138\n- https://github.com/caolan/async/commit/e1ecdbf79264f9ab488c7799f4c76996d5dca66d\n- https://github.com/caolan/async/blob/master/lib/internal/iterator.js\n- https://github.com/caolan/async/blob/master/lib/mapValuesLimit.js\n- https://jsfiddle.net/oz5twjd9/\n- https://github.com/caolan/async/pull/1828\n- https://github.com/caolan/async/commit/8f7f90342a6571ba1c197d747ebed30c368096d2\n- https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md#v264\n- https://github.com/caolan/async/compare/v2.6.3...v2.6.4\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MTEUUTNIEBHGKUKKLNUZSV7IEP6IP3Q3/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UM6XJ73Q3NAM5KSGCOKJ2ZIA6GUWUJLK/\n- https://github.com/advisories/GHSA-fwr7-v2mv-hh25","created":"2022-04-07T00:00:17.000Z","reported_by":null,"title":"Prototype Pollution in async","npm_advisory_id":null,"overview":"A vulnerability exists in Async through 3.2.1 for 3.x and through 2.6.3 for 2.x (fixed in 3.2.2 and 2.6.4), which could let a malicious user obtain privileges via the `mapValues()` method.","url":"https://github.com/advisories/GHSA-fwr7-v2mv-hh25"},"1092470":{"findings":[{"version":"2.4.3","paths":["@pact-foundation/pact-node>request>tough-cookie","@pact-foundation/pact>@pact-foundation/pact-node>request>tough-cookie"]}],"metadata":null,"vulnerable_versions":"<4.1.3","module_name":"tough-cookie","severity":"moderate","github_advisory_id":"GHSA-72xf-g2v4-qvf3","cves":["CVE-2023-26136"],"access":"public","patched_versions":">=4.1.3","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"},"updated":"2023-07-11T13:44:36.000Z","recommendation":"Upgrade to version 4.1.3 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1092470,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-26136\n- https://github.com/salesforce/tough-cookie/issues/282\n- https://github.com/salesforce/tough-cookie/commit/12d474791bb856004e858fdb1c47b7608d09cf6e\n- https://github.com/salesforce/tough-cookie/releases/tag/v4.1.3\n- https://security.snyk.io/vuln/SNYK-JS-TOUGHCOOKIE-5672873\n- https://lists.debian.org/debian-lts-announce/2023/07/msg00010.html\n- https://github.com/advisories/GHSA-72xf-g2v4-qvf3","created":"2023-07-01T06:30:16.000Z","reported_by":null,"title":"tough-cookie Prototype Pollution vulnerability","npm_advisory_id":null,"overview":"Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in `rejectPublicSuffixes=false` mode. This issue arises from the manner in which the objects are initialized.","url":"https://github.com/advisories/GHSA-72xf-g2v4-qvf3"},"1092549":{"findings":[{"version":"8.5.1","paths":["jsonwebtoken"]}],"metadata":null,"vulnerable_versions":"<9.0.0","module_name":"jsonwebtoken","severity":"moderate","github_advisory_id":"GHSA-qwph-4952-7xr6","cves":["CVE-2022-23540"],"access":"public","patched_versions":">=9.0.0","cvss":{"score":6.4,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:L"},"updated":"2023-07-14T22:03:14.000Z","recommendation":"Upgrade to version 9.0.0 or later","cwe":["CWE-287","CWE-327","CWE-347"],"found_by":null,"deleted":null,"id":1092549,"references":"- https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-qwph-4952-7xr6\n- https://github.com/auth0/node-jsonwebtoken/commit/e1fa9dcc12054a8681db4e6373da1b30cf7016e3\n- https://nvd.nist.gov/vuln/detail/CVE-2022-23540\n- https://github.com/advisories/GHSA-qwph-4952-7xr6","created":"2022-12-22T03:32:59.000Z","reported_by":null,"title":"jsonwebtoken vulnerable to signature validation bypass due to insecure default algorithm in jwt.verify()","npm_advisory_id":null,"overview":"# Overview\n\nIn versions <=8.5.1 of jsonwebtoken library, lack of algorithm definition and a falsy secret or key in the `jwt.verify()` function can lead to signature validation bypass due to defaulting to the `none` algorithm for signature verification.\n\n# Am I affected?\nYou will be affected if all the following are true in the `jwt.verify()` function:\n- a token with no signature is received\n- no algorithms are specified \n- a falsy (e.g. null, false, undefined) secret or key is passed \n\n# How do I fix it?\n \nUpdate to version 9.0.0 which removes the default support for the none algorithm in the `jwt.verify()` method. \n\n# Will the fix impact my users?\n\nThere will be no impact, if you update to version 9.0.0 and you don’t need to allow for the `none` algorithm. If you need 'none' algorithm, you have to explicitly specify that in `jwt.verify()` options.\n","url":"https://github.com/advisories/GHSA-qwph-4952-7xr6"},"1092636":{"findings":[{"version":"1.28.1","paths":["@hmcts/rpx-xui-node-lib>openid-client>jose"]}],"metadata":null,"vulnerable_versions":">=1.0.0 <=1.28.1","module_name":"jose","severity":"moderate","github_advisory_id":"GHSA-jv3g-j58f-9mq9","cves":["CVE-2022-36083"],"access":"public","patched_versions":">=1.28.2","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-07-21T21:33:36.000Z","recommendation":"Upgrade to version 1.28.2 or later","cwe":["CWE-400","CWE-834"],"found_by":null,"deleted":null,"id":1092636,"references":"- https://github.com/panva/jose/security/advisories/GHSA-jv3g-j58f-9mq9\n- https://nvd.nist.gov/vuln/detail/CVE-2022-36083\n- https://github.com/panva/jose/commit/03d6d013bf6e070e85adfe5731f526978e3e8e4d\n- https://github.com/panva/jose/releases/tag/v4.9.2\n- https://github.com/advisories/GHSA-jv3g-j58f-9mq9","created":"2022-09-16T17:44:42.000Z","reported_by":null,"title":"JOSE vulnerable to resource exhaustion via specifically crafted JWE","npm_advisory_id":null,"overview":"The PBKDF2-based JWE key management algorithms expect a JOSE Header Parameter named `p2c` ([PBES2 Count](https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2)), which determines how many PBKDF2 iterations must be executed in order to derive a CEK wrapping key. The purpose of this parameter is to intentionally slow down the key derivation function in order to make password brute-force and dictionary attacks more expensive.\n\nThis makes the PBES2 algorithms unsuitable for situations where the JWE is coming from an untrusted source: an adversary can intentionally pick an extremely high PBES2 Count value, that will initiate a CPU-bound computation that may take an unreasonable amount of time to finish.\n\n### Impact\n\nUnder certain conditions (see below) it is possible to have the user's environment consume unreasonable amount of CPU time.\n\n### Affected users\n\nThe impact is limited only to users utilizing the JWE decryption APIs with symmetric secrets to decrypt JWEs from untrusted parties who do not limit the accepted JWE Key Management Algorithms (`alg` Header Parameter) using the `keyManagementAlgorithms` (or `algorithms` in v1.x) decryption option or through other means.\n\nThe PBKDF2-based JWE Key Management Algorithm Identifiers are\n\n- `PBES2-HS256+A128KW`\n- `PBES2-HS384+A192KW`\n- `PBES2-HS512+A256KW`\n\ne.g.\n\n```js\nconst secret = new Uint8Array(16)\nconst jwe = '...' // JWE from an untrusted party\n\nawait jose.compactDecrypt(jwe, secret)\n```\n\nYou are NOT affected if any of the following applies to you\n\n- Your code does not use the JWE APIs\n- Your code only produces JWE tokens\n- Your code only decrypts JWEs using an asymmetric JWE Key Management Algorithm (this means you're providing an asymmetric key object to the JWE decryption API)\n- Your code only accepts JWEs produced by trusted sources\n- Your code limits the accepted JWE Key Management Algorithms using the `keyManagementAlgorithms` decryption option not including any of the PBKDF2-based JWE key management algorithms\n\n### Patches\n\n`v1.28.2`, `v2.0.6`, `v3.20.4`, and `v4.9.2` releases limit the maximum PBKDF2 iteration count to `10000` by default. It is possible to adjust this limit with a newly introduced `maxPBES2Count` decryption option.\n\n### Workarounds\n\nAll users should be able to upgrade given all stable semver major release lines have had new a patch release introduced which limits the PBKDF2 iteration count to `10000` by default. This removes the ability to craft JWEs that would consume unreasonable amount of CPU time.\n\nIf users are unable to upgrade their required library version they have two options depending on whether they expect to receive JWEs using any of the three PBKDF2-based JWE key management algorithms.\n\n- they can use the `keyManagementAlgorithms` decryption option to disable accepting PBKDF2 altogether\n- they can inspect the JOSE Header prior to using the decryption API and limit the PBKDF2 iteration count (`p2c` Header Parameter)\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an discussion in the project's [repository](https://github.com/panva/jose/discussions/new?category=q-a&title=GHSA-jv3g-j58f-9mq9%20advisory%20question)\n* Email me at [panva.ip@gmail.com](mailto:panva.ip@gmail.com)\n","url":"https://github.com/advisories/GHSA-jv3g-j58f-9mq9"},"1092964":{"findings":[{"version":"0.7.0","paths":["ngx-md>marked"]}],"metadata":null,"vulnerable_versions":"<4.0.10","module_name":"marked","severity":"high","github_advisory_id":"GHSA-5v2h-r2cx-5xgj","cves":["CVE-2022-21681"],"access":"public","patched_versions":">=4.0.10","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-08-14T05:04:30.000Z","recommendation":"Upgrade to version 4.0.10 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1092964,"references":"- https://github.com/markedjs/marked/security/advisories/GHSA-5v2h-r2cx-5xgj\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21681\n- https://github.com/markedjs/marked/commit/8f806573a3f6c6b7a39b8cdb66ab5ebb8d55a5f5\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AIXDMC3CSHYW3YWVSQOXAWLUYQHAO5UX/\n- https://github.com/advisories/GHSA-5v2h-r2cx-5xgj","created":"2022-01-14T21:04:46.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in marked","npm_advisory_id":null,"overview":"### Impact\n\n_What kind of vulnerability is it?_\n\nDenial of service.\n\nThe regular expression `inline.reflinkSearch` may cause catastrophic backtracking against some strings.\nPoC is the following.\n\n```javascript\nimport * as marked from 'marked';\n\nconsole.log(marked.parse(`[x]: x\n\n\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](\\\\[\\\\](`));\n```\n\n_Who is impacted?_\n\nAnyone who runs untrusted markdown through marked and does not use a worker with a time limit.\n\n### Patches\n\n_Has the problem been patched?_\n\nYes\n\n_What versions should users upgrade to?_\n\n4.0.10\n\n### Workarounds\n\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\nDo not run untrusted markdown through marked or run marked on a [worker](https://marked.js.org/using_advanced#workers) thread and set a reasonable time limit to prevent draining resources.\n\n### References\n\n_Are there any links users can visit to find out more?_\n\n- https://marked.js.org/using_advanced#workers\n- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [marked](https://github.com/markedjs/marked)\n","url":"https://github.com/advisories/GHSA-5v2h-r2cx-5xgj"},"1092969":{"findings":[{"version":"0.7.0","paths":["ngx-md>marked"]}],"metadata":null,"vulnerable_versions":"<4.0.10","module_name":"marked","severity":"high","github_advisory_id":"GHSA-rrrm-qjm4-v8hf","cves":["CVE-2022-21680"],"access":"public","patched_versions":">=4.0.10","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-08-14T05:03:59.000Z","recommendation":"Upgrade to version 4.0.10 or later","cwe":["CWE-400","CWE-1333"],"found_by":null,"deleted":null,"id":1092969,"references":"- https://github.com/markedjs/marked/security/advisories/GHSA-rrrm-qjm4-v8hf\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21680\n- https://github.com/markedjs/marked/commit/c4a3ccd344b6929afa8a1d50ac54a721e57012c0\n- https://github.com/markedjs/marked/releases/tag/v4.0.10\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AIXDMC3CSHYW3YWVSQOXAWLUYQHAO5UX/\n- https://github.com/advisories/GHSA-rrrm-qjm4-v8hf","created":"2022-01-14T21:04:41.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in marked","npm_advisory_id":null,"overview":"### Impact\n\n_What kind of vulnerability is it?_\n\nDenial of service.\n\nThe regular expression `block.def` may cause catastrophic backtracking against some strings.\nPoC is the following.\n\n```javascript\nimport * as marked from \"marked\";\n\nmarked.parse(`[x]:${' '.repeat(1500)}x ${' '.repeat(1500)} x`);\n```\n\n_Who is impacted?_\n\nAnyone who runs untrusted markdown through marked and does not use a worker with a time limit.\n\n### Patches\n\n_Has the problem been patched?_\n\nYes\n\n_What versions should users upgrade to?_\n\n4.0.10\n\n### Workarounds\n\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\nDo not run untrusted markdown through marked or run marked on a [worker](https://marked.js.org/using_advanced#workers) thread and set a reasonable time limit to prevent draining resources.\n\n### References\n\n_Are there any links users can visit to find out more?_\n\n- https://marked.js.org/using_advanced#workers\n- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [marked](https://github.com/markedjs/marked)\n","url":"https://github.com/advisories/GHSA-rrrm-qjm4-v8hf"},"1092972":{"findings":[{"version":"2.88.2","paths":["@pact-foundation/pact-node>request","@pact-foundation/pact>@pact-foundation/pact-node>request"]}],"metadata":null,"vulnerable_versions":"<=2.88.2","module_name":"request","severity":"moderate","github_advisory_id":"GHSA-p8p7-x288-28g6","cves":["CVE-2023-28155"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":6.1,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"},"updated":"2023-08-14T20:53:47.000Z","recommendation":"None","cwe":["CWE-918"],"found_by":null,"deleted":null,"id":1092972,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-28155\n- https://github.com/request/request/issues/3442\n- https://github.com/request/request/pull/3444\n- https://doyensec.com/resources/Doyensec_Advisory_RequestSSRF_Q12023.pdf\n- https://security.netapp.com/advisory/ntap-20230413-0007/\n- https://github.com/github/advisory-database/pull/2500\n- https://github.com/cypress-io/request/blob/master/lib/redirect.js#L116\n- https://github.com/request/request/blob/master/lib/redirect.js#L111\n- https://github.com/cypress-io/request/pull/28\n- https://github.com/cypress-io/request/commit/c5bcf21d40fb61feaff21a0e5a2b3934a440024f\n- https://github.com/cypress-io/request/releases/tag/v3.0.0\n- https://github.com/advisories/GHSA-p8p7-x288-28g6","created":"2023-03-16T15:30:19.000Z","reported_by":null,"title":"Server-Side Request Forgery in Request","npm_advisory_id":null,"overview":"The `request` package through 2.88.2 for Node.js and the `@cypress/request` package prior to 3.0.0 allow a bypass of SSRF mitigations via an attacker-controller server that does a cross-protocol redirect (HTTP to HTTPS, or HTTPS to HTTP).\n\nNOTE: The `request` package is no longer supported by the maintainer.","url":"https://github.com/advisories/GHSA-p8p7-x288-28g6"},"1092990":{"findings":[{"version":"6.0.5","paths":["@pact-foundation/pact-node>tar","@pact-foundation/pact>@pact-foundation/pact-node>tar","playwright>fsevents>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>tar","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>tar"]}],"metadata":null,"vulnerable_versions":">=6.0.0 <6.1.9","module_name":"tar","severity":"high","github_advisory_id":"GHSA-qq89-hq3f-393p","cves":["CVE-2021-37712"],"access":"public","patched_versions":">=6.1.9","cvss":{"score":8.2,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"},"updated":"2023-08-15T22:50:55.000Z","recommendation":"Upgrade to version 6.1.9 or later","cwe":["CWE-22","CWE-59"],"found_by":null,"deleted":null,"id":1092990,"references":"- https://github.com/npm/node-tar/security/advisories/GHSA-qq89-hq3f-393p\n- https://www.npmjs.com/package/tar\n- https://nvd.nist.gov/vuln/detail/CVE-2021-37712\n- https://www.oracle.com/security-alerts/cpuoct2021.html\n- https://www.debian.org/security/2021/dsa-5008\n- https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00023.html\n- https://github.com/isaacs/node-tar/commit/1739408d3122af897caefd09662bce2ea477533b\n- https://github.com/isaacs/node-tar/commit/b6162c7fafe797f856564ef37f4b82747f051455\n- https://github.com/isaacs/node-tar/commit/bb93ba243746f705092905da1955ac3b0509ba1e\n- https://github.com/advisories/GHSA-qq89-hq3f-393p","created":"2021-08-31T16:05:17.000Z","reported_by":null,"title":"Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning using symbolic links","npm_advisory_id":null,"overview":"### Impact\nArbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution\n\nnode-tar aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary stat calls to determine whether a given path is a directory, paths are cached when directories are created.\n\nThis logic was insufficient when extracting tar files that contained two directories and a symlink with names containing unicode values that normalized to the same value. Additionally, on Windows systems, long path portions would resolve to the same file system entities as their 8.3 \"short path\" counterparts. A specially crafted tar archive could thus include directories with two forms of the path that resolve to the same file system entity, followed by a symbolic link with a name in the first form, lastly followed by a file using the second form. It led to bypassing node-tar symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.\n\nThe v3 branch of `node-tar` has been deprecated and did not receive patches for these issues. If you are still using a v3 release we recommend you update to a more recent version of `node-tar`. If this is not possible, a workaround is available below.\n\n### Patches\n\n6.1.9 || 5.0.10 || 4.4.18\n\n### Workarounds\n\nUsers may work around this vulnerability without upgrading by creating a custom filter method which prevents the extraction of symbolic links.\n\n```js\nconst tar = require('tar')\n\ntar.x({\n file: 'archive.tgz',\n filter: (file, entry) => {\n if (entry.type === 'SymbolicLink') {\n return false\n } else {\n return true\n }\n }\n})\n```\n\nUsers are encouraged to upgrade to the latest patched versions, rather than attempt to sanitize tar input themselves.\n\n#### Fix\n\nThe problem is addressed in the following ways, when comparing paths in the directory cache and path reservation systems:\n\n1. The `String.normalize('NFKD')` method is used to first normalize all unicode to its maximally compatible and multi-code-point form.\n2. All slashes are normalized to `/` on Windows systems (on posix systems, `\\` is a valid filename character, and thus left intact).\n3. When a symbolic link is encountered on Windows systems, the entire directory cache is cleared. Collisions related to use of 8.3 short names to replace directories with other (non-symlink) types of entries may make archives fail to extract properly, but will not result in arbitrary file writes.\n","url":"https://github.com/advisories/GHSA-qq89-hq3f-393p"},"1093264":{"findings":[{"version":"7.0.0","paths":["global-agent>semver","@hmcts/nodejs-healthcheck>superagent>semver","playwright>fsevents>node-gyp>semver","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>semver","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>semver","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>semver"]}],"metadata":null,"vulnerable_versions":">=7.0.0 <7.5.2","module_name":"semver","severity":"moderate","github_advisory_id":"GHSA-c2qf-rxjj-qqgw","cves":["CVE-2022-25883"],"access":"public","patched_versions":">=7.5.2","cvss":{"score":5.3,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-09-01T23:43:55.000Z","recommendation":"Upgrade to version 7.5.2 or later","cwe":["CWE-1333"],"found_by":null,"deleted":null,"id":1093264,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25883\n- https://github.com/npm/node-semver/pull/564\n- https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441\n- https://security.snyk.io/vuln/SNYK-JS-SEMVER-3247795\n- https://github.com/npm/node-semver/blob/main/classes/range.js#L97-L104\n- https://github.com/npm/node-semver/blob/main/internal/re.js#L138\n- https://github.com/npm/node-semver/blob/main/internal/re.js#L160\n- https://github.com/npm/node-semver/pull/585\n- https://github.com/npm/node-semver/commit/928e56d21150da0413a3333a3148b20e741a920c\n- https://github.com/npm/node-semver/pull/593\n- https://github.com/npm/node-semver/commit/2f8fd41487acf380194579ecb6f8b1bbfe116be0\n- https://github.com/advisories/GHSA-c2qf-rxjj-qqgw","created":"2023-06-21T06:30:28.000Z","reported_by":null,"title":"semver vulnerable to Regular Expression Denial of Service","npm_advisory_id":null,"overview":"Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.","url":"https://github.com/advisories/GHSA-c2qf-rxjj-qqgw"},"1093429":{"findings":[{"version":"2.6.1","paths":["node-fetch","isomorphic-fetch>node-fetch"]}],"metadata":null,"vulnerable_versions":"<2.6.7","module_name":"node-fetch","severity":"high","github_advisory_id":"GHSA-r683-j2x4-v87g","cves":["CVE-2022-0235"],"access":"public","patched_versions":">=2.6.7","cvss":{"score":8.8,"vectorString":"CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"updated":"2023-09-07T18:38:42.000Z","recommendation":"Upgrade to version 2.6.7 or later","cwe":["CWE-173","CWE-200","CWE-601"],"found_by":null,"deleted":null,"id":1093429,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-0235\n- https://github.com/node-fetch/node-fetch/commit/36e47e8a6406185921e4985dcbeff140d73eaa10\n- https://huntr.dev/bounties/d26ab655-38d6-48b3-be15-f9ad6b6ae6f7\n- https://github.com/node-fetch/node-fetch/pull/1453\n- https://github.com/node-fetch/node-fetch/commit/5c32f002fdd65b1c6a8f1e3620210813d45c7e60\n- https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf\n- https://lists.debian.org/debian-lts-announce/2022/12/msg00007.html\n- https://github.com/node-fetch/node-fetch/pull/1449/commits/5c32f002fdd65b1c6a8f1e3620210813d45c7e60\n- https://github.com/node-fetch/node-fetch/commit/1ef4b560a17e644a02a3bfdea7631ffeee578b35\n- https://github.com/advisories/GHSA-r683-j2x4-v87g","created":"2022-01-21T23:55:52.000Z","reported_by":null,"title":"node-fetch forwards secure headers to untrusted sites","npm_advisory_id":null,"overview":"node-fetch forwards secure headers such as `authorization`, `www-authenticate`, `cookie`, & `cookie2` when redirecting to a untrusted site.","url":"https://github.com/advisories/GHSA-r683-j2x4-v87g"},"1093500":{"findings":[{"version":"0.15.6","paths":["xlsx"]}],"metadata":null,"vulnerable_versions":"<0.19.3","module_name":"xlsx","severity":"high","github_advisory_id":"GHSA-4r6h-8v6p-xvw6","cves":["CVE-2023-30533"],"access":"public","patched_versions":">=0.19.3","cvss":{"score":7.8,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"},"updated":"2023-09-07T21:28:03.000Z","recommendation":"Upgrade to version 0.19.3 or later","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1093500,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-30533\n- https://cdn.sheetjs.com/advisories/CVE-2023-30533\n- https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/CHANGELOG.md\n- https://git.sheetjs.com/sheetjs/sheetjs/issues/2667\n- https://git.sheetjs.com/sheetjs/sheetjs/issues/2986\n- https://github.com/advisories/GHSA-4r6h-8v6p-xvw6","created":"2023-04-24T09:30:19.000Z","reported_by":null,"title":"Prototype Pollution in sheetJS","npm_advisory_id":null,"overview":"All versions of SheetJS CE through 0.19.2 are vulnerable to \"Prototype Pollution\" when reading specially crafted files. Workflows that do not read arbitrary files (for example, exporting data to spreadsheet files) are unaffected.\n\nA non-vulnerable version cannot be found via npm, as the repository hosted on GitHub and the npm package `xlsx` are no longer maintained.","url":"https://github.com/advisories/GHSA-4r6h-8v6p-xvw6"},"1093639":{"findings":[{"version":"0.4.1","paths":["@hmcts/rpx-xui-node-lib>passport"]}],"metadata":null,"vulnerable_versions":"<0.6.0","module_name":"passport","severity":"moderate","github_advisory_id":"GHSA-v923-w3x8-wh69","cves":["CVE-2022-25896"],"access":"public","patched_versions":">=0.6.0","cvss":{"score":4.8,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L"},"updated":"2023-09-11T16:22:18.000Z","recommendation":"Upgrade to version 0.6.0 or later","cwe":["CWE-384"],"found_by":null,"deleted":null,"id":1093639,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-25896\n- https://github.com/jaredhanson/passport/pull/900\n- https://github.com/jaredhanson/passport/commit/7e9b9cf4d7be02428e963fc729496a45baeea608\n- https://snyk.io/vuln/SNYK-JS-PASSPORT-2840631\n- https://github.com/advisories/GHSA-v923-w3x8-wh69","created":"2022-07-02T00:00:19.000Z","reported_by":null,"title":"Passport vulnerable to session regeneration when a users logs in or out","npm_advisory_id":null,"overview":"This affects the package passport before 0.6.0. When a user logs in or logs out, the session is regenerated instead of being closed.","url":"https://github.com/advisories/GHSA-v923-w3x8-wh69"},"1094087":{"findings":[{"version":"0.2.0","paths":["http-proxy-middleware>micromatch>snapdragon>source-map-resolve>decode-uri-component","http-proxy-middleware>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>ts-auto-mock>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>braces>snapdragon>source-map-resolve>decode-uri-component","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>extglob>expand-brackets>snapdragon>source-map-resolve>decode-uri-component"]}],"metadata":null,"vulnerable_versions":"<0.2.1","module_name":"decode-uri-component","severity":"high","github_advisory_id":"GHSA-w573-4hg7-7wgq","cves":["CVE-2022-38900"],"access":"public","patched_versions":">=0.2.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-09-21T22:16:39.000Z","recommendation":"Upgrade to version 0.2.1 or later","cwe":["CWE-20"],"found_by":null,"deleted":null,"id":1094087,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-38900\n- https://github.com/SamVerschueren/decode-uri-component/issues/5\n- https://github.com/sindresorhus/query-string/issues/345\n- https://github.com/SamVerschueren/decode-uri-component/commit/746ca5dcb6667c5d364e782d53c542830e4c10b9\n- https://github.com/SamVerschueren/decode-uri-component/releases/tag/v0.2.1\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ERN6YE3DS7NBW7UH44SCJBMNC2NWQ7SM/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KAC5KQ2SEWAMQ6UZAUBZ5KXKEOESH375/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VNV2GNZXOTEDAJRFH3ZYWRUBGIVL7BSU/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QABOUA2I542UTANVZIVFKWMRYVHLV32D/\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UW4SCMT3SEUFVIL7YIADQ5K36GJEO6I5/\n- https://github.com/advisories/GHSA-w573-4hg7-7wgq","created":"2022-11-28T15:30:24.000Z","reported_by":null,"title":"decode-uri-component vulnerable to Denial of Service (DoS)","npm_advisory_id":null,"overview":"decode-uri-component 0.2.0 is vulnerable to Improper Input Validation resulting in DoS.","url":"https://github.com/advisories/GHSA-w573-4hg7-7wgq"},"1094091":{"findings":[{"version":"4.1.0","paths":["@pact-foundation/pact>cli-color>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>strip-ansi>ansi-regex","playwright>fsevents>node-gyp>npmlog>gauge>strip-ansi>ansi-regex","playwright>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@pact-foundation/pact-node>bunyan>dtrace-provider>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@pact-foundation/pact>@pact-foundation/pact-node>bunyan>dtrace-provider>nan>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>string-width>strip-ansi>ansi-regex","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>npmlog>gauge>wide-align>string-width>strip-ansi>ansi-regex"]}],"metadata":null,"vulnerable_versions":">=4.0.0 <4.1.1","module_name":"ansi-regex","severity":"high","github_advisory_id":"GHSA-93q8-gq69-wqmw","cves":["CVE-2021-3807"],"access":"public","patched_versions":">=4.1.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2023-09-21T22:14:52.000Z","recommendation":"Upgrade to version 4.1.1 or later","cwe":["CWE-697","CWE-1333"],"found_by":null,"deleted":null,"id":1094091,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2021-3807\n- https://github.com/chalk/ansi-regex/commit/8d1d7cdb586269882c4bdc1b7325d0c58c8f76f9\n- https://huntr.dev/bounties/5b3cf33b-ede0-4398-9974-800876dfd994\n- https://github.com/chalk/ansi-regex/issues/38#issuecomment-924086311\n- https://app.snyk.io/vuln/SNYK-JS-ANSIREGEX-1583908\n- https://github.com/chalk/ansi-regex/issues/38#issuecomment-925924774\n- https://github.com/chalk/ansi-regex/releases/tag/v6.0.1\n- https://www.oracle.com/security-alerts/cpuapr2022.html\n- https://security.netapp.com/advisory/ntap-20221014-0002/\n- https://github.com/chalk/ansi-regex/commit/419250fa510bf31b4cc672e76537a64f9332e1f1\n- https://github.com/chalk/ansi-regex/commit/75a657da7af875b2e2724fd6331bf0a4b23d3c9a\n- https://github.com/chalk/ansi-regex/commit/c3c0b3f2736b9c01feec0fef33980c43720dcde8\n- https://github.com/advisories/GHSA-93q8-gq69-wqmw","created":"2021-09-20T20:20:09.000Z","reported_by":null,"title":"Inefficient Regular Expression Complexity in chalk/ansi-regex","npm_advisory_id":null,"overview":"ansi-regex is vulnerable to Inefficient Regular Expression Complexity which could lead to a denial of service when parsing invalid ANSI escape codes.\n\n**Proof of Concept**\n```js\nimport ansiRegex from 'ansi-regex';\nfor(var i = 1; i <= 50000; i++) {\n var time = Date.now();\n var attack_str = \"\\u001B[\"+\";\".repeat(i*10000);\n ansiRegex().test(attack_str)\n var time_cost = Date.now() - time;\n console.log(\"attack_str.length: \" + attack_str.length + \": \" + time_cost+\" ms\")\n}\n```\nThe ReDOS is mainly due to the sub-patterns `[[\\\\]()#;?]*` and `(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]*)*`","url":"https://github.com/advisories/GHSA-93q8-gq69-wqmw"},"1094219":{"findings":[{"version":"4.2.0","paths":["express-session>debug","@hmcts/nodejs-healthcheck>superagent>debug","@hmcts/rpx-xui-node-lib>express>body-parser>debug","@hmcts/rpx-xui-node-lib>express>serve-static>send>debug","@hmcts/rpx-xui-node-lib>ts-auto-mock>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-ts-auto-mock>ts-auto-mock>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/environment>@jest/fake-timers>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>braces>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>expect>jest-message-util>micromatch>extglob>expand-brackets>snapdragon>debug","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>jest-haste-map>fsevents>node-gyp>make-fetch-happen>http-proxy-agent>agent-base>debug"]}],"metadata":null,"vulnerable_versions":">=4.0.0 <4.3.1","module_name":"debug","severity":"moderate","github_advisory_id":"GHSA-gxpj-cx7g-858c","cves":["CVE-2017-16137"],"access":"public","patched_versions":">=4.3.1","cvss":{"score":5.3,"vectorString":"CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"},"updated":"2023-10-02T17:59:03.000Z","recommendation":"Upgrade to version 4.3.1 or later","cwe":["CWE-400"],"found_by":null,"deleted":null,"id":1094219,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2017-16137\n- https://github.com/visionmedia/debug/issues/501\n- https://github.com/visionmedia/debug/pull/504\n- https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3@%3Ccommits.netbeans.apache.org%3E\n- https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63@%3Cnotifications.netbeans.apache.org%3E\n- https://github.com/debug-js/debug/issues/797\n- https://github.com/debug-js/debug/commit/4e2150207c568adb9ead8f4c4528016081c88020\n- https://github.com/debug-js/debug/commit/71169065b5262f9858ac78cc0b688c84a438f290\n- https://github.com/debug-js/debug/commit/b6d12fdbc63b483e5c969da33ea6adc09946b5ac\n- https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a\n- https://github.com/advisories/GHSA-gxpj-cx7g-858c","created":"2018-08-09T20:18:07.000Z","reported_by":null,"title":"Regular Expression Denial of Service in debug","npm_advisory_id":null,"overview":"Affected versions of `debug` are vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. \n\nAs it takes 50,000 characters to block the event loop for 2 seconds, this issue is a low severity issue.\n\nThis was later re-introduced in version v3.2.0, and then repatched in versions 3.2.7 and 4.3.1.\n\n## Recommendation\n\nVersion 2.x.x: Update to version 2.6.9 or later.\nVersion 3.1.x: Update to version 3.1.0 or later.\nVersion 3.2.x: Update to version 3.2.7 or later.\nVersion 4.x.x: Update to version 4.3.1 or later.","url":"https://github.com/advisories/GHSA-gxpj-cx7g-858c"},"1094415":{"findings":[{"version":"7.22.10","paths":["@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>@babel/helpers>@babel/traverse","@hmcts/rpx-xui-node-lib>jest-mock-axios>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-runtime>@jest/globals>@jest/expect>jest-snapshot>@jest/transform>babel-plugin-istanbul>istanbul-lib-instrument>@babel/core>@babel/helpers>@babel/traverse"]}],"metadata":null,"vulnerable_versions":"<7.23.2","module_name":"@babel/traverse","severity":"critical","github_advisory_id":"GHSA-67hx-6x53-jw92","cves":["CVE-2023-45133"],"access":"public","patched_versions":">=7.23.2","cvss":{"score":9.3,"vectorString":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"},"updated":"2023-10-20T13:27:09.000Z","recommendation":"Upgrade to version 7.23.2 or later","cwe":["CWE-184"],"found_by":null,"deleted":null,"id":1094415,"references":"- https://github.com/babel/babel/security/advisories/GHSA-67hx-6x53-jw92\n- https://nvd.nist.gov/vuln/detail/CVE-2023-45133\n- https://github.com/babel/babel/pull/16033\n- https://github.com/babel/babel/commit/b13376b346946e3f62fc0848c1d2a23223314c82\n- https://github.com/babel/babel/releases/tag/v7.23.2\n- https://github.com/babel/babel/releases/tag/v8.0.0-alpha.4\n- https://www.debian.org/security/2023/dsa-5528\n- https://lists.debian.org/debian-lts-announce/2023/10/msg00026.html\n- https://babeljs.io/blog/2023/10/16/cve-2023-45133\n- https://github.com/advisories/GHSA-67hx-6x53-jw92","created":"2023-10-16T13:55:36.000Z","reported_by":null,"title":"Babel vulnerable to arbitrary code execution when compiling specifically crafted malicious code","npm_advisory_id":null,"overview":"### Impact\n\nUsing Babel to compile code that was specifically crafted by an attacker can lead to arbitrary code execution during compilation, when using plugins that rely on the `path.evaluate()`or `path.evaluateTruthy()` internal Babel methods.\n\nKnown affected plugins are:\n- `@babel/plugin-transform-runtime`\n- `@babel/preset-env` when using its [`useBuiltIns`](https://babeljs.io/docs/babel-preset-env#usebuiltins) option\n- Any \"polyfill provider\" plugin that depends on `@babel/helper-define-polyfill-provider`, such as `babel-plugin-polyfill-corejs3`, `babel-plugin-polyfill-corejs2`, `babel-plugin-polyfill-es-shims`, `babel-plugin-polyfill-regenerator`\n\nNo other plugins under the `@babel/` namespace are impacted, but third-party plugins might be.\n\n**Users that only compile trusted code are not impacted.**\n\n### Patches\n\nThe vulnerability has been fixed in `@babel/traverse@7.23.2`.\n\nBabel 6 does not receive security fixes anymore (see [Babel's security policy](https://github.com/babel/babel/security/policy)), hence there is no patch planned for `babel-traverse@6`.\n\n### Workarounds\n\n- Upgrade `@babel/traverse` to v7.23.2 or higher. You can do this by deleting it from your package manager's lockfile and re-installing the dependencies. `@babel/core` >=7.23.2 will automatically pull in a non-vulnerable version.\n- If you cannot upgrade `@babel/traverse` and are using one of the affected packages mentioned above, upgrade them to their latest version to avoid triggering the vulnerable code path in affected `@babel/traverse` versions:\n - `@babel/plugin-transform-runtime` v7.23.2\n - `@babel/preset-env` v7.23.2\n - `@babel/helper-define-polyfill-provider` v0.4.3\n - `babel-plugin-polyfill-corejs2` v0.4.6\n - `babel-plugin-polyfill-corejs3` v0.8.5\n - `babel-plugin-polyfill-es-shims` v0.10.0\n - `babel-plugin-polyfill-regenerator` v0.5.3","url":"https://github.com/advisories/GHSA-67hx-6x53-jw92"}},"muted":[],"metadata":{"vulnerabilities":{"info":0,"low":0,"moderate":52,"high":136,"critical":16},"dependencies":838,"devDependencies":7,"optionalDependencies":0,"totalDependencies":845}} diff --git a/yarn.lock b/yarn.lock index cb9142b3a..04ca04071 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2307,16 +2307,16 @@ __metadata: languageName: node linkType: hard -"@hmcts/rpx-xui-common-lib@npm:1.9.0-address-display-full": - version: 1.9.0-address-display-full - resolution: "@hmcts/rpx-xui-common-lib@npm:1.9.0-address-display-full" +"@hmcts/rpx-xui-common-lib@npm:1.9.0-route-expected-feature-2": + version: 1.9.0-route-expected-feature-2 + resolution: "@hmcts/rpx-xui-common-lib@npm:1.9.0-route-expected-feature-2" dependencies: tslib: ^2.0.0 peerDependencies: launchdarkly-js-client-sdk: ^2.15.2 ngx-pagination: ^3.2.1 rpx-xui-translation: ^0.1.1 - checksum: 804edc259bd7981357455bd4e83bb95c5b647b37efa95d302c937bdb3b348eac71a1722e6459e5a3a3ff47cc72b7808cef947717ad2d39646f278c0b9e525dcd + checksum: 6408bf39a6fb459bc624022d4b964e1d034e3d35980a3b602606a9483234981774d12713903b876ebe5bdbc920ff8b68c4ba3d11aa210b3c07219cb1d10d02b8 languageName: node linkType: hard @@ -11053,6 +11053,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.1, fsevents@npm:~2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: latest + checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:^1.2.7": version: 1.2.13 resolution: "fsevents@npm:1.2.13" @@ -11064,22 +11074,21 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.1, fsevents@npm:~2.3.2": - version: 2.3.2 - resolution: "fsevents@npm:2.3.2" +"fsevents@npm:~2.1.2": + version: 2.1.3 + resolution: "fsevents@npm:2.1.3" dependencies: node-gyp: latest - checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f + checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard -"fsevents@npm:~2.1.2": - version: 2.1.3 - resolution: "fsevents@npm:2.1.3" +"fsevents@patch:fsevents@2.3.2#~builtin, fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.1#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: node-gyp: latest - checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard @@ -11094,15 +11103,6 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.1#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" - dependencies: - node-gyp: latest - conditions: os=darwin - languageName: node - linkType: hard - "fsevents@patch:fsevents@~2.1.2#~builtin": version: 2.1.3 resolution: "fsevents@patch:fsevents@npm%3A2.1.3#~builtin::version=2.1.3&hash=31d12a" @@ -18090,6 +18090,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.38.1": + version: 1.38.1 + resolution: "playwright-core@npm:1.38.1" + bin: + playwright-core: cli.js + checksum: 66e83fe040f309b13ad94ba39dea40ac207bfcbbc22de13141af88dbdedd64e1c4e3ce1d0cb070d4efd8050d7e579953ec3681dd8a0acf2c1cc738d9c50e545e + languageName: node + linkType: hard + +"playwright@npm:^1.18": + version: 1.38.1 + resolution: "playwright@npm:1.38.1" + dependencies: + fsevents: 2.3.2 + playwright-core: 1.38.1 + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 4e01d4ee52d9ccf75a80d8492829106802590721d56bff7c5957ff1f21eb3c328ee5bc3c1784a59c4b515df1b98d08ef92e4a35a807f454cd00dc481d30fadc2 + languageName: node + linkType: hard + "pnp-webpack-plugin@npm:1.6.4": version: 1.6.4 resolution: "pnp-webpack-plugin@npm:1.6.4" @@ -20363,7 +20387,7 @@ __metadata: "@hmcts/frontend": 0.0.50-alpha "@hmcts/nodejs-healthcheck": 1.7.0 "@hmcts/properties-volume": 0.0.13 - "@hmcts/rpx-xui-common-lib": 1.9.0-address-display-full + "@hmcts/rpx-xui-common-lib": 1.9.0-route-expected-feature-2 "@hmcts/rpx-xui-node-lib": 2.27.1 "@ng-idle/core": ^10.0.0 "@ng-idle/keepalive": ^10.0.0 @@ -20468,6 +20492,7 @@ __metadata: p-iteration: ^1.1.7 pa11y: ^5.3.0 pa11y-reporter-html: ^2.0.0 + playwright: ^1.18 portfinder: ^1.0.28 protractor: ~7.0.0 protractor-cucumber-framework: 6.1.1