Skip to content

Commit 00a5849

Browse files
committed
More tests to explore issues with members awaiting training
1 parent 748adce commit 00a5849

File tree

1 file changed

+183
-1
lines changed

1 file changed

+183
-1
lines changed

tests/read-models/shared-state/get-equipment.test.ts

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {getSomeOrFail} from '../../helpers';
77
import {EmailAddress} from '../../../src/types';
88
import {Int} from 'io-ts';
99
import {updateState} from '../../../src/read-models/shared-state/update-state';
10-
import {EventOfType} from '../../../src/types/domain-event';
10+
import {constructEvent, EventOfType} from '../../../src/types/domain-event';
11+
import {trainingQuizTable} from '../../../src/read-models/shared-state/state';
1112

1213
describe('get', () => {
1314
let framework: TestFramework;
@@ -47,6 +48,21 @@ describe('get', () => {
4748
equipmentId: equipmentId,
4849
memberNumber: addTrainedMember.memberNumber,
4950
};
51+
const addTrainingSheet = {
52+
equipmentId,
53+
trainingSheetId: 'testTrainingSheetId',
54+
};
55+
const passedQuizResult = {
56+
id: faker.string.uuid() as UUID,
57+
equipmentId: addEquipment.id,
58+
trainingSheetId: addTrainingSheet.trainingSheetId,
59+
memberNumberProvided: addTrainerMember.memberNumber,
60+
emailProvided: addTrainerMember.email,
61+
score: 10,
62+
maxScore: 10,
63+
percentage: 100,
64+
timestampEpochMS: 1739621371,
65+
};
5066

5167
beforeEach(async () => {
5268
framework = await initTestFramework();
@@ -401,4 +417,170 @@ describe('get', () => {
401417
}
402418
});
403419
});
420+
421+
describe('User has completed the quiz and passed', () => {
422+
beforeEach(async () => {
423+
await framework.commands.memberNumbers.linkNumberToEmail(
424+
addTrainedMember
425+
);
426+
await framework.commands.area.create(createArea);
427+
await framework.commands.equipment.add(addEquipment);
428+
await framework.commands.equipment.trainingSheet(addTrainingSheet);
429+
updateState(framework.sharedReadModel.db)(
430+
constructEvent('EquipmentTrainingQuizResult')(passedQuizResult)
431+
);
432+
});
433+
434+
describe('User is already trained', () => {
435+
beforeEach(async () => {
436+
await framework.commands.trainers.markTrained(markTrained);
437+
});
438+
439+
it("User doesn't appear as awaiting training", () => {
440+
expect(
441+
getSomeOrFail(
442+
framework.sharedReadModel.equipment.get(addEquipment.id)
443+
).membersAwaitingTraining
444+
).toHaveLength(0);
445+
});
446+
});
447+
448+
describe('User is not already trained', () => {
449+
it('User appears as awaiting training', () => {
450+
const awaitingTraining = getSomeOrFail(
451+
framework.sharedReadModel.equipment.get(addEquipment.id)
452+
).membersAwaitingTraining;
453+
expect(awaitingTraining).toHaveLength(1);
454+
expect(awaitingTraining[0].memberNumber).toStrictEqual(
455+
addTrainedMember.memberNumber
456+
);
457+
});
458+
});
459+
});
460+
461+
describe('Check equipment quiz result event idempotency', () => {
462+
beforeEach(async () => {
463+
await framework.commands.memberNumbers.linkNumberToEmail(
464+
addTrainerMember
465+
);
466+
await framework.commands.area.create(createArea);
467+
await framework.commands.equipment.add(addEquipment);
468+
});
469+
470+
[true, false].forEach(quizIdDuplicate => {
471+
describe(`Duplicate of same event, quiz id duplicate ${quizIdDuplicate}`, () => {
472+
beforeEach(() => {
473+
const update = updateState(framework.sharedReadModel.db);
474+
for (let i = 0; i < 2; i++) {
475+
if (quizIdDuplicate) {
476+
update(
477+
constructEvent('EquipmentTrainingQuizResult')(passedQuizResult)
478+
);
479+
} else {
480+
update(
481+
constructEvent('EquipmentTrainingQuizResult')({
482+
...passedQuizResult,
483+
id: faker.string.uuid() as UUID,
484+
})
485+
);
486+
}
487+
}
488+
});
489+
it('The user is marked as waiting for training exactly once', () => {
490+
const awaitingTraining = getSomeOrFail(
491+
framework.sharedReadModel.equipment.get(addEquipment.id)
492+
).membersAwaitingTraining;
493+
expect(awaitingTraining).toHaveLength(1);
494+
expect(awaitingTraining[0].memberNumber).toStrictEqual(
495+
addTrainerMember.memberNumber
496+
);
497+
});
498+
it("The shared read model database doesn't contain duplicate entries", () => {
499+
// Strictly speaking this is looking at the internals however its important we don't let the shared db just grow infinitely
500+
const rows = framework.sharedReadModel.db
501+
.select()
502+
.from(trainingQuizTable)
503+
.all();
504+
expect(rows.length).toHaveLength(1);
505+
});
506+
});
507+
});
508+
});
509+
510+
describe("User passes equipment quiz twice and hasn't been trained yet", () => {
511+
beforeEach(async () => {
512+
await framework.commands.memberNumbers.linkNumberToEmail(
513+
addTrainerMember
514+
);
515+
await framework.commands.area.create(createArea);
516+
await framework.commands.equipment.add(addEquipment); // We add the equipment but never register a training sheet.
517+
for (let i = 0; i < 2; i++) {
518+
updateState(framework.sharedReadModel.db)(
519+
constructEvent('EquipmentTrainingQuizResult')({
520+
...passedQuizResult,
521+
id: faker.string.uuid() as UUID,
522+
timestampEpochMS: faker.number.int({
523+
// Random epoch timestamp.
524+
min: 1500000000,
525+
max: 1900000000,
526+
}),
527+
})
528+
);
529+
}
530+
});
531+
// Note that we don't specifically care in this case if the training quiz result is added to the shared read
532+
// state once or twice since they are technically different events but (currently) produce the same outcome.
533+
it('The user is marked as waiting for training exactly once', () => {
534+
const awaitingTraining = getSomeOrFail(
535+
framework.sharedReadModel.equipment.get(addEquipment.id)
536+
).membersAwaitingTraining;
537+
expect(awaitingTraining).toHaveLength(1);
538+
expect(awaitingTraining[0].memberNumber).toStrictEqual(
539+
addTrainerMember.memberNumber
540+
);
541+
});
542+
});
543+
544+
describe('Check equipment quiz results for no sheet but correct equipment', () => {
545+
beforeEach(async () => {
546+
await framework.commands.memberNumbers.linkNumberToEmail(
547+
addTrainerMember
548+
);
549+
await framework.commands.area.create(createArea);
550+
await framework.commands.equipment.add(addEquipment); // We add the equipment but never register a training sheet.
551+
updateState(framework.sharedReadModel.db)(
552+
constructEvent('EquipmentTrainingQuizResult')(passedQuizResult)
553+
);
554+
});
555+
it('No users are marked as waiting for training', () => {
556+
expect(
557+
getSomeOrFail(framework.sharedReadModel.equipment.get(addEquipment.id))
558+
.membersAwaitingTraining
559+
).toHaveLength(0);
560+
});
561+
});
562+
563+
describe('Check equipment quiz results for different sheet but correct equipment', () => {
564+
beforeEach(async () => {
565+
await framework.commands.memberNumbers.linkNumberToEmail(
566+
addTrainerMember
567+
);
568+
await framework.commands.area.create(createArea);
569+
await framework.commands.equipment.add(addEquipment);
570+
await framework.commands.equipment.trainingSheet({
571+
equipmentId: addEquipment.id,
572+
trainingSheetId: faker.string.uuid(), // A different training sheet id.
573+
});
574+
updateState(framework.sharedReadModel.db)(
575+
// Events have ended up in the db somehow from an unknown (perhaps removed) training sheet.
576+
constructEvent('EquipmentTrainingQuizResult')(passedQuizResult)
577+
);
578+
});
579+
it('No users are marked as waiting for training', () => {
580+
expect(
581+
getSomeOrFail(framework.sharedReadModel.equipment.get(addEquipment.id))
582+
.membersAwaitingTraining
583+
).toHaveLength(0);
584+
});
585+
});
404586
});

0 commit comments

Comments
 (0)