@@ -7,7 +7,8 @@ import {getSomeOrFail} from '../../helpers';
77import { EmailAddress } from '../../../src/types' ;
88import { Int } from 'io-ts' ;
99import { 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
1213describe ( '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