Skip to content

Commit

Permalink
Merge pull request #6464 from NMDSdevopsServiceAdm/feat/1576-keep-all…
Browse files Browse the repository at this point in the history
…-job-roles-bugfix-in-new-mandatory-training

Feat/1576 keep all job roles bugfix in new mandatory training
  • Loading branch information
duncanc19 authored Jan 7, 2025
2 parents 24e40c8 + fe48687 commit d7e807f
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 61 deletions.
44 changes: 43 additions & 1 deletion backend/server/routes/establishments/mandatoryTraining/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,63 @@ const { hasPermission } = require('../../../utils/security/hasPermission');

const viewMandatoryTraining = async (req, res) => {
const establishmentId = req.establishmentId;
const previousAllJobsLengths = [29, 31, 32];

try {
const allMandatoryTrainingRecords = await MandatoryTraining.fetch(establishmentId);
let allMandatoryTrainingRecords = await MandatoryTraining.fetch(establishmentId);
let duplicateJobRolesUpdated = false;

if (allMandatoryTrainingRecords?.mandatoryTraining?.length) {
for (mandatoryTrainingCategory of allMandatoryTrainingRecords.mandatoryTraining) {
if (
hasDuplicateJobs(mandatoryTrainingCategory.jobs) &&
previousAllJobsLengths.includes(mandatoryTrainingCategory.jobs.length)
) {
duplicateJobRolesUpdated = true;

const thisMandatoryTrainingRecord = new MandatoryTraining(establishmentId);
await thisMandatoryTrainingRecord.load({
trainingCategoryId: mandatoryTrainingCategory.trainingCategoryId,
allJobRoles: true,
jobs: [],
});
await thisMandatoryTrainingRecord.save(req.userUid);
}
}

if (duplicateJobRolesUpdated) {
allMandatoryTrainingRecords = await MandatoryTraining.fetch(establishmentId);
}
}

return res.status(200).json(allMandatoryTrainingRecords);
} catch (err) {
console.error(err);
return res.status(500).send();
}
};

const hasDuplicateJobs = (jobRoles) => {
const seenJobIds = new Set();

for (const jobRole of jobRoles) {
if (seenJobIds.has(jobRole.id)) {
return true; // Duplicate found
}
seenJobIds.add(jobRole.id);
}

return false; // No duplicates found
};

/**
* Handle GET request for getting all saved mandatory training for view all mandatory training
*/
const viewAllMandatoryTraining = async (req, res) => {
const establishmentId = req.establishmentId;
try {
const allMandatoryTrainingRecords = await MandatoryTraining.fetchAllMandatoryTrainings(establishmentId);

return res.status(200).json(allMandatoryTrainingRecords);
} catch (err) {
console.error(err);
Expand Down Expand Up @@ -93,3 +134,4 @@ router.route('/:categoryId').delete(deleteMandatoryTrainingById);
module.exports = router;
module.exports.createAndUpdateMandatoryTraining = createAndUpdateMandatoryTraining;
module.exports.deleteMandatoryTrainingById = deleteMandatoryTrainingById;
module.exports.viewMandatoryTraining = viewMandatoryTraining;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const sinon = require('sinon');
const {
createAndUpdateMandatoryTraining,
deleteMandatoryTrainingById,
viewMandatoryTraining,
} = require('../../../../../routes/establishments/mandatoryTraining/index.js');

const MandatoryTraining = require('../../../../../models/classes/mandatoryTraining').MandatoryTraining;
Expand Down Expand Up @@ -34,6 +35,7 @@ describe('mandatoryTraining/index.js', () => {
expect(res.statusCode).to.deep.equal(500);
});
});

describe('createAndUpdateMandatoryTraining', () => {
it('should save the record for mandatory training if isvalid , not exists and all job role is selected', async () => {
sinon.stub(MandatoryTraining.prototype, 'load').callsFake(() => {
Expand Down Expand Up @@ -84,4 +86,120 @@ describe('mandatoryTraining/index.js', () => {
expect(res.statusCode).to.deep.equal(500);
});
});

describe('viewMandatoryTraining', () => {
let req;
let res;

beforeEach(() => {
req = httpMocks.createRequest();
req.establishmentId = 'mockId';
req.userUid = 'abc123';
res = httpMocks.createResponse();
});

const createMockFetchData = () => {
return {
allJobRolesCount: 37,
lastUpdated: '2025-01-03T11:55:55.734Z',
mandatoryTraining: [
{
category: 'Activity provision, wellbeing',
establishmentId: 100,
jobs: [{ id: 22, title: 'Registered manager' }],
trainingCategoryId: 1,
},
],
mandatoryTrainingCount: 1,
};
};

it('should fetch all mandatory training for establishment passed in request', async () => {
const fetchSpy = sinon.stub(MandatoryTraining, 'fetch').callsFake(() => createMockFetchData());

await viewMandatoryTraining(req, res);
expect(res.statusCode).to.deep.equal(200);
expect(fetchSpy).to.have.been.calledWith(req.establishmentId);
});

it('should return data from fetch and 200 status if fetch successful', async () => {
const mockFetchData = createMockFetchData();
sinon.stub(MandatoryTraining, 'fetch').callsFake(() => mockFetchData);

await viewMandatoryTraining(req, res);
expect(res.statusCode).to.equal(200);
expect(res._getJSONData()).to.deep.equal(mockFetchData);
});

it('should return 500 status if error when fetching data', async () => {
sinon.stub(MandatoryTraining, 'fetch').throws('Unexpected error');

await viewMandatoryTraining(req, res);
expect(res.statusCode).to.equal(500);
});

describe('Handling duplicate job roles', () => {
it('should not call load or save for mandatory training when no duplicate job roles in retrieved mandatory training', async () => {
const loadSpy = sinon.stub(MandatoryTraining.prototype, 'load');
const saveSpy = sinon.stub(MandatoryTraining.prototype, 'save');

sinon.stub(MandatoryTraining, 'fetch').callsFake(() => createMockFetchData());

await viewMandatoryTraining(req, res);
expect(loadSpy).not.to.have.been.called;
expect(saveSpy).not.to.have.been.called;
});

const previousAllJobsLengths = [29, 31, 32];

previousAllJobsLengths.forEach((allJobsLength) => {
it(`should call load and save for mandatory training instance when duplicate job roles in retrieved mandatory training and has length of previous all job roles (${allJobsLength})`, async () => {
const loadSpy = sinon.stub(MandatoryTraining.prototype, 'load');
const saveSpy = sinon.stub(MandatoryTraining.prototype, 'save');

const mockFetchData = createMockFetchData();

const mockJobRoles = Array.from({ length: allJobsLength - 1 }, (_, index) => ({
id: index + 1,
title: `Job role ${index + 1}`,
}));
mockJobRoles.push({ id: 1, title: 'Job role 1' });
mockFetchData.mandatoryTraining[0].jobs = mockJobRoles;

const fetchSpy = sinon.stub(MandatoryTraining, 'fetch').callsFake(() => mockFetchData);

await viewMandatoryTraining(req, res);

expect(loadSpy).to.have.been.calledWith({
trainingCategoryId: mockFetchData.mandatoryTraining[0].trainingCategoryId,
allJobRoles: true,
jobs: [],
});
expect(saveSpy).to.have.been.calledWith(req.userUid);
expect(fetchSpy).to.have.been.calledTwice;
});
});

it('should not call load or save for mandatory training when number of jobs is previous all job roles length (29) but no duplicate job roles', async () => {
const loadSpy = sinon.stub(MandatoryTraining.prototype, 'load');
const saveSpy = sinon.stub(MandatoryTraining.prototype, 'save');

const mockFetchData = createMockFetchData();

const mockJobRoles = Array.from({ length: 29 }, (_, index) => ({
id: index + 1,
title: `Job role ${index + 1}`,
}));

mockFetchData.mandatoryTraining[0].jobs = mockJobRoles;

sinon.stub(MandatoryTraining, 'fetch').callsFake(() => mockFetchData);

await viewMandatoryTraining(req, res);

expect(loadSpy).not.to.have.been.called;
expect(saveSpy).not.to.have.been.called;
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,10 @@ <h1 class="govuk-heading-l">
</td>

<td class="govuk-table__cell">
<ng-container
*ngIf="
records.jobs.length === allJobsLength ||
(mandatoryTrainingHasDuplicateJobRoles[i][records.trainingCategoryId].hasPreviousAllJobsLength &&
mandatoryTrainingHasDuplicateJobRoles[i][records.trainingCategoryId].hasDuplicates)
"
>
<span data-testid="titleAll">{{ 'All' }}</span>
<ng-container *ngIf="records.jobs.length === allJobsLength">
<span data-testid="titleAll">All</span>
</ng-container>
<ng-container
*ngIf="
records.jobs.length < allJobsLength &&
!mandatoryTrainingHasDuplicateJobRoles[i][records.trainingCategoryId].hasPreviousAllJobsLength
"
>
<ng-container *ngIf="records.jobs.length < allJobsLength">
<div data-testid="titleJob" *ngFor="let job of records.jobs">{{ job.title }}</div>
</ng-container>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ import { MandatoryTrainingService } from '@core/services/training.service';
import { WindowRef } from '@core/services/window.ref';
import { MockBreadcrumbService } from '@core/test-utils/MockBreadcrumbService';
import { establishmentBuilder, MockEstablishmentService } from '@core/test-utils/MockEstablishmentService';
import {
mockMandatoryTraining,
MockMandatoryTrainingService,
MockTrainingService,
} from '@core/test-utils/MockTrainingService';
import { mockMandatoryTraining, MockMandatoryTrainingService } from '@core/test-utils/MockTrainingService';
import { SharedModule } from '@shared/shared.module';
import { fireEvent, render } from '@testing-library/angular';

Expand Down Expand Up @@ -45,9 +41,7 @@ describe('AddAndManageMandatoryTrainingComponent', () => {
},
{
provide: MandatoryTrainingService,
useFactory: overrides.duplicateJobRoles
? MockTrainingService.factory(overrides.duplicateJobRoles)
: MockMandatoryTrainingService.factory(),
useFactory: MockMandatoryTrainingService.factory(),
},
{
provide: EstablishmentService,
Expand Down Expand Up @@ -222,17 +216,5 @@ describe('AddAndManageMandatoryTrainingComponent', () => {
);
});
});

describe('Handling duplicate job roles', () => {
it('should show all if there are any duplicate job roles and the job roles length is the old all job roles length', async () => {
const { getByTestId } = await setup({ duplicateJobRoles: true });

const coshCategory = getByTestId('titleAll');
const autismCategory = getByTestId('titleJob');

expect(coshCategory.textContent).toContain('All');
expect(autismCategory.textContent).toContain('Activities worker, coordinator');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export class AddAndManageMandatoryTrainingComponent implements OnInit {
public establishment: Establishment;
public existingMandatoryTrainings: any;
public allJobsLength: Number;
public mandatoryTrainingHasDuplicateJobRoles = [];
public previousAllJobsLength = [29, 31, 32];

constructor(
public trainingService: MandatoryTrainingService,
Expand All @@ -34,30 +32,6 @@ export class AddAndManageMandatoryTrainingComponent implements OnInit {
this.existingMandatoryTrainings = this.route.snapshot.data?.existingMandatoryTraining;
this.sortTrainingAlphabetically(this.existingMandatoryTrainings.mandatoryTraining);
this.allJobsLength = this.existingMandatoryTrainings.allJobRolesCount;
this.setMandatoryTrainingHasDuplicateJobRoles();
}

public checkDuplicateJobRoles(jobs): boolean {
for (let i = 0; i < jobs.length; i++) {
for (let j = i + 1; j < jobs.length; j++) {
if (jobs[i].id === jobs[j].id) {
return true;
}
}
}
}

public setMandatoryTrainingHasDuplicateJobRoles() {
let mandatoryTraining = this.existingMandatoryTrainings.mandatoryTraining;

mandatoryTraining.forEach((trainingCategory, index) => {
this.mandatoryTrainingHasDuplicateJobRoles.push({
[trainingCategory.trainingCategoryId]: {
hasDuplicates: this.checkDuplicateJobRoles(trainingCategory.jobs),
hasPreviousAllJobsLength: this.previousAllJobsLength.includes(trainingCategory.jobs.length),
},
});
});
}

public sortTrainingAlphabetically(training) {
Expand Down

0 comments on commit d7e807f

Please sign in to comment.