@@ -28,11 +28,15 @@ import {
2828 DONE as DISCOVERY_DONE ,
2929 updateDiscoveryConnection
3030} from 'shared/modules/discovery/discoveryDuck'
31+ import forceResetPasswordQueryHelper , {
32+ MultiDatabaseNotSupportedError
33+ } from './forceResetPasswordQueryHelper'
3134
3235jest . mock ( 'services/bolt/bolt' , ( ) => {
3336 return {
3437 closeConnection : jest . fn ( ) ,
35- openConnection : jest . fn ( )
38+ openConnection : jest . fn ( ) ,
39+ directConnect : jest . fn ( )
3640 }
3741} )
3842
@@ -457,3 +461,310 @@ describe('switchConnectionEpic', () => {
457461 return p
458462 } )
459463} )
464+
465+ describe ( 'handleForcePasswordChangeEpic' , ( ) => {
466+ const bus = createBus ( )
467+ const epicMiddleware = createEpicMiddleware (
468+ connections . handleForcePasswordChangeEpic
469+ )
470+ const mockStore = configureMockStore ( [
471+ epicMiddleware ,
472+ createReduxMiddleware ( bus )
473+ ] )
474+
475+ let store : any
476+
477+ const $$responseChannel = 'test-channel'
478+ const action = {
479+ host : 'bolt://localhost:7687' ,
480+ type : connections . FORCE_CHANGE_PASSWORD ,
481+ password : 'changeme' ,
482+ newPassword : 'password1' ,
483+ $$responseChannel
484+ }
485+
486+ const executePasswordResetQuerySpy = jest . spyOn (
487+ forceResetPasswordQueryHelper ,
488+ 'executePasswordResetQuery'
489+ )
490+
491+ const executeAlterCurrentUserQuerySpy = jest . spyOn (
492+ forceResetPasswordQueryHelper ,
493+ 'executeAlterCurrentUserQuery'
494+ )
495+
496+ const executeCallChangePasswordQuerySpy = jest . spyOn (
497+ forceResetPasswordQueryHelper ,
498+ 'executeCallChangePasswordQuery'
499+ )
500+
501+ const mockSessionClose = jest . fn ( )
502+ const mockSessionExecuteWrite = jest . fn ( )
503+
504+ const mockDriver = {
505+ session : jest . fn ( ) . mockReturnValue ( {
506+ close : mockSessionClose ,
507+ executeWrite : mockSessionExecuteWrite
508+ } ) ,
509+ close : jest . fn ( ) . mockReturnValue ( true )
510+ }
511+
512+ beforeAll ( ( ) => {
513+ store = mockStore ( { } )
514+ } )
515+
516+ beforeEach ( ( ) => {
517+ ; ( bolt . directConnect as jest . Mock ) . mockResolvedValue ( mockDriver )
518+ } )
519+
520+ afterEach ( ( ) => {
521+ store . clearActions ( )
522+ bus . reset ( )
523+ jest . clearAllMocks ( )
524+ } )
525+
526+ test ( 'handleForcePasswordChangeEpic resolves with an error if directConnect fails' , ( ) => {
527+ // Given
528+ const message = 'An error occurred.'
529+ ; ( bolt . directConnect as jest . Mock ) . mockRejectedValue ( new Error ( message ) )
530+
531+ const p = new Promise < void > ( ( resolve , reject ) => {
532+ bus . take ( $$responseChannel , currentAction => {
533+ // Then
534+ const actions = store . getActions ( )
535+ try {
536+ expect ( actions ) . toEqual ( [ action , currentAction ] )
537+
538+ expect ( executeAlterCurrentUserQuerySpy ) . not . toHaveBeenCalled ( )
539+
540+ expect ( executeCallChangePasswordQuerySpy ) . not . toHaveBeenCalled ( )
541+
542+ expect ( executePasswordResetQuerySpy ) . not . toHaveBeenCalled ( )
543+
544+ expect ( currentAction ) . toEqual ( {
545+ error : expect . objectContaining ( {
546+ message
547+ } ) ,
548+ success : false ,
549+ type : $$responseChannel
550+ } )
551+
552+ resolve ( )
553+
554+ expect ( mockDriver . close ) . not . toHaveBeenCalled ( )
555+ expect ( mockSessionClose ) . not . toHaveBeenCalled ( )
556+ } catch ( e ) {
557+ reject ( e )
558+ }
559+ } )
560+ } )
561+
562+ // When
563+ epicMiddleware . replaceEpic ( connections . handleForcePasswordChangeEpic )
564+ store . clearActions ( )
565+ store . dispatch ( action )
566+
567+ // Return
568+ return p
569+ } )
570+
571+ test ( 'handleForcePasswordChangeEpic resolves when successfully executing cypher query' , ( ) => {
572+ // Given
573+ mockSessionExecuteWrite . mockResolvedValue ( true )
574+
575+ const p = new Promise < void > ( ( resolve , reject ) => {
576+ bus . take ( $$responseChannel , currentAction => {
577+ // Then
578+ const actions = store . getActions ( )
579+ try {
580+ expect ( actions ) . toEqual ( [ action , currentAction ] )
581+
582+ expect ( executeAlterCurrentUserQuerySpy ) . toHaveBeenCalledTimes ( 1 )
583+
584+ expect ( executeCallChangePasswordQuerySpy ) . not . toHaveBeenCalled ( )
585+
586+ expect ( executePasswordResetQuerySpy ) . toHaveBeenCalledTimes ( 1 )
587+
588+ expect ( executePasswordResetQuerySpy ) . toHaveBeenCalledWith (
589+ expect . anything ( ) ,
590+ expect . objectContaining ( {
591+ parameters : { newPw : 'password1' , oldPw : 'changeme' } ,
592+ query : 'ALTER CURRENT USER SET PASSWORD FROM $oldPw TO $newPw'
593+ } ) ,
594+ expect . anything ( ) ,
595+ { database : 'system' }
596+ )
597+
598+ expect ( currentAction ) . toEqual ( {
599+ result : { meta : 'bolt://localhost:7687' } ,
600+ success : true ,
601+ type : $$responseChannel
602+ } )
603+
604+ resolve ( )
605+
606+ expect ( mockDriver . close ) . toHaveBeenCalledTimes ( 1 )
607+ expect ( mockSessionClose ) . toHaveBeenCalledTimes ( 1 )
608+ } catch ( e ) {
609+ reject ( e )
610+ }
611+ } )
612+ } )
613+
614+ // When
615+ epicMiddleware . replaceEpic ( connections . handleForcePasswordChangeEpic )
616+ store . clearActions ( )
617+ store . dispatch ( action )
618+
619+ // Return
620+ return p
621+ } )
622+
623+ test ( 'handleForcePasswordChangeEpic resolves with an error if cypher query fails' , ( ) => {
624+ // Given
625+ const message = 'A password must be at least 8 characters.'
626+ mockSessionExecuteWrite
627+ . mockRejectedValueOnce ( new Error ( message ) )
628+ . mockResolvedValue ( true )
629+
630+ const p = new Promise < void > ( ( resolve , reject ) => {
631+ bus . take ( $$responseChannel , currentAction => {
632+ // Then
633+ const actions = store . getActions ( )
634+ try {
635+ expect ( actions ) . toEqual ( [ action , currentAction ] )
636+
637+ expect ( executeAlterCurrentUserQuerySpy ) . toHaveBeenCalledTimes ( 1 )
638+
639+ expect ( executeCallChangePasswordQuerySpy ) . not . toHaveBeenCalled ( )
640+
641+ expect ( executePasswordResetQuerySpy ) . toHaveBeenCalledTimes ( 1 )
642+
643+ expect ( currentAction ) . toEqual ( {
644+ error : expect . objectContaining ( {
645+ message
646+ } ) ,
647+ success : false ,
648+ type : $$responseChannel
649+ } )
650+
651+ resolve ( )
652+
653+ expect ( mockDriver . close ) . toHaveBeenCalledTimes ( 1 )
654+ expect ( mockSessionClose ) . toHaveBeenCalledTimes ( 1 )
655+ } catch ( e ) {
656+ reject ( e )
657+ }
658+ } )
659+ } )
660+
661+ // When
662+ epicMiddleware . replaceEpic ( connections . handleForcePasswordChangeEpic )
663+ store . clearActions ( )
664+ store . dispatch ( action )
665+
666+ // Return
667+ return p
668+ } )
669+
670+ test ( 'handleForcePasswordChangeEpic resolves when successfully falling back to dbms function call' , ( ) => {
671+ // Given
672+ mockSessionExecuteWrite
673+ . mockRejectedValueOnce ( new MultiDatabaseNotSupportedError ( ) )
674+ . mockResolvedValue ( true )
675+
676+ const p = new Promise < void > ( ( resolve , reject ) => {
677+ bus . take ( $$responseChannel , currentAction => {
678+ // Then
679+ const actions = store . getActions ( )
680+ try {
681+ expect ( actions ) . toEqual ( [ action , currentAction ] )
682+
683+ expect ( executeAlterCurrentUserQuerySpy ) . toHaveBeenCalledTimes ( 1 )
684+
685+ expect ( executeCallChangePasswordQuerySpy ) . toHaveBeenCalledTimes ( 1 )
686+
687+ expect ( executePasswordResetQuerySpy ) . toHaveBeenCalledTimes ( 2 )
688+
689+ expect ( executePasswordResetQuerySpy ) . toHaveBeenLastCalledWith (
690+ expect . anything ( ) ,
691+ expect . objectContaining ( {
692+ parameters : { password : 'password1' } ,
693+ query : 'CALL dbms.security.changePassword($password)'
694+ } ) ,
695+ expect . anything ( ) ,
696+ undefined
697+ )
698+
699+ expect ( currentAction ) . toEqual ( {
700+ result : { meta : 'bolt://localhost:7687' } ,
701+ success : true ,
702+ type : $$responseChannel
703+ } )
704+
705+ resolve ( )
706+
707+ expect ( mockDriver . close ) . toHaveBeenCalledTimes ( 1 )
708+ expect ( mockSessionClose ) . toHaveBeenCalledTimes ( 2 )
709+ } catch ( e ) {
710+ reject ( e )
711+ }
712+ } )
713+ } )
714+
715+ // When
716+ epicMiddleware . replaceEpic ( connections . handleForcePasswordChangeEpic )
717+ store . clearActions ( )
718+ store . dispatch ( action )
719+
720+ // Return
721+ return p
722+ } )
723+
724+ test ( 'handleForcePasswordChangeEpic resolves with an error if dbms function call fails' , ( ) => {
725+ // Given
726+ const message = 'A password must be at least 8 characters.'
727+ mockSessionExecuteWrite
728+ . mockRejectedValueOnce ( new MultiDatabaseNotSupportedError ( ) )
729+ . mockRejectedValue ( new Error ( message ) )
730+
731+ const p = new Promise < void > ( ( resolve , reject ) => {
732+ bus . take ( $$responseChannel , currentAction => {
733+ // Then
734+ const actions = store . getActions ( )
735+ try {
736+ expect ( actions ) . toEqual ( [ action , currentAction ] )
737+
738+ expect ( executeAlterCurrentUserQuerySpy ) . toHaveBeenCalledTimes ( 1 )
739+
740+ expect ( executeCallChangePasswordQuerySpy ) . toHaveBeenCalledTimes ( 1 )
741+
742+ expect ( executePasswordResetQuerySpy ) . toHaveBeenCalledTimes ( 2 )
743+
744+ expect ( currentAction ) . toEqual ( {
745+ error : expect . objectContaining ( {
746+ message
747+ } ) ,
748+ success : false ,
749+ type : $$responseChannel
750+ } )
751+
752+ resolve ( )
753+
754+ expect ( mockDriver . close ) . toHaveBeenCalledTimes ( 1 )
755+ expect ( mockSessionClose ) . toHaveBeenCalledTimes ( 2 )
756+ } catch ( e ) {
757+ reject ( e )
758+ }
759+ } )
760+ } )
761+
762+ // When
763+ epicMiddleware . replaceEpic ( connections . handleForcePasswordChangeEpic )
764+ store . clearActions ( )
765+ store . dispatch ( action )
766+
767+ // Return
768+ return p
769+ } )
770+ } )
0 commit comments