import { OpenYoloCredential , OpenYoloCredentialHintOptions , OpenYoloCredentialRequestOptions , OpenYoloProxyLoginResponse , RequestContext } from '../protocol/data' ; import { OpenYoloInternalError } from '../protocol/errors' ; import { SecureChannel } from '../protocol/secure_channel' ; import { PromiseResolver , TimeoutRacer } from '../protocol/utils' ; import { FakeOpenYoloApi , InitializeOnDemandApi , isCompatibleBrowser , OnDemandOpenYoloApi , openyolo , OpenYoloApi , OpenYoloApiImpl , OpenYoloWithTimeoutApi } from './api' ; import { RelayRequest } from './base_request' ; import { CancelLastOperationRequest } from './cancel_last_operation_request' ; import { CredentialRequest } from './credential_request' ; import { CredentialSave } from './credential_save' ; import { DisableAutoSignIn } from './disable_auto_sign_in' ; import { HintAvailableRequest } from './hint_available_request' ; import { HintRequest } from './hint_request' ; import { ProviderFrameElement } from './provider_frame_elem' ; import { ProxyLogin } from './proxy_login' ; type OpenYoloWithTimeoutApiMethods = keyof OpenYoloWithTimeoutApi ; describe ( 'OpenYolo API' , ( ) => { const credential = { id : 'test' , authMethod : 'test' } ; const expectedError = new Error ( 'ERROR!' ) ; const secureChannelSpy = jasmine . createSpyObj ( 'SecureChannel' , [ 'send' , 'listen' , 'dispose' ] ) ; describe ( 'setTimeouts' , ( ) => { it ( 'raises an error if given a negative number' , ( ) => { expect ( ( ) => { openyolo . setTimeouts ( - 1 ) ; } ) . toThrowError ( ) ; } ) ; it ( 'resets if changes to disable' , ( ) => { spyOn ( openyolo , 'reset' ) ; openyolo . setTimeouts ( 0 ) ; expect ( openyolo . reset ) . toHaveBeenCalled ( ) ; openyolo . setTimeouts ( 0 ) ; expect ( openyolo . reset ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'does not reset if changes to positive value' , ( ) => { spyOn ( openyolo , 'reset' ) ; openyolo . setTimeouts ( 1 ) ; expect ( openyolo . reset ) . not . toHaveBeenCalled ( ) ; } ) ; } ) ; describe ( 'initialization' , ( ) => { let openYoloApiImplSpy ; beforeEach ( ( ) => { openyolo . setTimeouts ( null ) ; openYoloApiImplSpy = jasmine . createSpyObj ( 'OpenYoloApiImpl' , [ 'hintsAvailable' , 'hint' , 'retrieve' , 'save' , 'proxyLogin' , 'disableAutoSignIn' , 'cancelLastOperation' , 'dispose' ] ) ; } ) ; it ( 'allows to initialize again if failed first time' , ( done ) => { spyOn ( InitializeOnDemandApi , 'createOpenYoloApi' ) . and . returnValue ( Promise . reject ( expectedError ) ) ; openyolo . cancelLastOperation ( ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( expectedError ) ; ( InitializeOnDemandApi . createOpenYoloApi as jasmine . Spy ) . and . returnValue ( Promise . resolve ( openYoloApiImplSpy ) ) ; openYoloApiImplSpy . disableAutoSignIn . and . returnValue ( Promise . resolve ( ) ) ; return openyolo . cancelLastOperation ( ) ; } ) . then ( done ) ; } ) ; it ( 'secure channel connection fails' , ( done ) => { spyOn ( SecureChannel , 'clientConnect' ) . and . returnValue ( Promise . reject ( expectedError ) ) ; spyOn ( ProviderFrameElement . prototype , 'dispose' ) ; openyolo . cancelLastOperation ( ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( expectedError ) ; expect ( ProviderFrameElement . prototype . dispose ) . toHaveBeenCalled ( ) ; openyolo . reset ( ) ; done ( ) ; } ) ; } ) ; describe ( 'timeouts' , ( ) => { beforeEach ( ( ) => { jasmine . clock ( ) . install ( ) ; } ) ; afterEach ( ( ) => { jasmine . clock ( ) . uninstall ( ) ; } ) ; it ( 'timeouts disabled' , ( done ) => { const promiseResolver = new PromiseResolver < void > ( ) ; spyOn ( SecureChannel , 'clientConnect' ) . and . returnValue ( promiseResolver . promise ) ; spyOn ( CancelLastOperationRequest . prototype , 'dispatch' ) . and . returnValue ( Promise . resolve ( ) ) ; openyolo . setTimeouts ( 0 ) ; openyolo . cancelLastOperation ( ) . then ( ( ) => { openyolo . reset ( ) ; done ( ) ; } , ( error ) => { console . log ( error ) ; done . fail ( 'Should not reject!' ) ; } ) ; jasmine . clock ( ) . tick ( Infinity ) ; promiseResolver . resolve ( secureChannelSpy ) ; } ) ; it ( 'custom timeouts set' , ( done ) => { const promiseResolver = new PromiseResolver < void > ( ) ; spyOn ( SecureChannel , 'clientConnect' ) . and . returnValue ( promiseResolver . promise ) ; let timeoutExpired = false ; openyolo . setTimeouts ( 100 ) ; openyolo . cancelLastOperation ( ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( timeoutExpired ) . toBe ( true ) ; openyolo . reset ( ) ; done ( ) ; } ) ; jasmine . clock ( ) . tick ( 99 ) ; timeoutExpired = true ; jasmine . clock ( ) . tick ( 1 ) ; promiseResolver . resolve ( secureChannelSpy ) ; } ) ; } ) ; describe ( 'successful initialization' , ( ) => { beforeEach ( ( ) => { spyOn ( InitializeOnDemandApi , 'createOpenYoloApi' ) . and . returnValue ( Promise . resolve ( openYoloApiImplSpy ) ) ; } ) ; it ( 'disableAutoSignIn' , ( done ) => { openYoloApiImplSpy . disableAutoSignIn . and . returnValue ( Promise . resolve ( ) ) ; openyolo . disableAutoSignIn ( ) . then ( ( ) => { expect ( openYoloApiImplSpy . disableAutoSignIn ) . toHaveBeenCalledWith ( jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'hintsAvailable' , ( done ) => { const options = { supportedAuthMethods : [ ] } ; openYoloApiImplSpy . hintsAvailable . and . returnValue ( Promise . resolve ( true ) ) ; openyolo . hintsAvailable ( options ) . then ( ( result ) => { expect ( result ) . toBe ( true ) ; expect ( openYoloApiImplSpy . hintsAvailable ) . toHaveBeenCalledWith ( options , jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'hint' , ( done ) => { const options = { supportedAuthMethods : [ ] , context : RequestContext . signIn } ; openYoloApiImplSpy . hint . and . returnValue ( Promise . resolve ( credential ) ) ; openyolo . hint ( options ) . then ( ( cred ) => { expect ( cred ) . toBe ( credential ) ; expect ( openYoloApiImplSpy . hint ) . toHaveBeenCalledWith ( options , jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'retrieve' , ( done ) => { const options = { supportedAuthMethods : [ ] } ; openYoloApiImplSpy . retrieve . and . returnValue ( Promise . resolve ( credential ) ) ; openyolo . retrieve ( options ) . then ( ( cred ) => { expect ( cred ) . toBe ( credential ) ; expect ( openYoloApiImplSpy . retrieve ) . toHaveBeenCalledWith ( options , jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'proxyLogin' , ( done ) => { const expectedResponse = { statusCode : 200 , responseText : 'test' } ; openYoloApiImplSpy . proxyLogin . and . returnValue ( Promise . resolve ( expectedResponse ) ) ; openyolo . proxyLogin ( credential ) . then ( ( response ) => { expect ( response ) . toBe ( expectedResponse ) ; expect ( openYoloApiImplSpy . proxyLogin ) . toHaveBeenCalledWith ( credential , jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'save' , ( done ) => { openYoloApiImplSpy . save . and . returnValue ( Promise . resolve ( ) ) ; openyolo . save ( credential ) . then ( ( ) => { expect ( openYoloApiImplSpy . save ) . toHaveBeenCalledWith ( credential , jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; it ( 'cancelLastOperation' , ( done ) => { openYoloApiImplSpy . cancelLastOperation . and . returnValue ( Promise . resolve ( ) ) ; openyolo . cancelLastOperation ( ) . then ( ( ) => { expect ( openYoloApiImplSpy . cancelLastOperation ) . toHaveBeenCalledWith ( jasmine . any ( Object ) ) ; done ( ) ; } ) ; } ) ; } ) ; } ) ; describe ( 'OpenYoloApiImpl' , ( ) => { const wrapBrowserError = OpenYoloInternalError . browserWrappingRequired ( ) . toExposedError ( ) ; const otherError = OpenYoloInternalError . noCredentialsAvailable ( ) . toExposedError ( ) ; let openYoloApiImpl ; let timeoutRacerSpy ; let navCredentialsSpy ; beforeEach ( ( ) => { navCredentialsSpy = jasmine . createSpyObj ( 'NavigatorCredentials' , [ 'hintsAvailable' , 'hint' , 'retrieve' , 'save' , 'proxyLogin' , 'disableAutoSignIn' , 'cancelLastOperation' ] ) ; const frameManager = jasmine . createSpyObj ( 'ProviderFrameElement' , [ 'display' ] ) ; openYoloApiImpl = new OpenYoloApiImpl ( frameManager , secureChannelSpy , navCredentialsSpy ) ; timeoutRacerSpy = jasmine . createSpyObj ( 'TimeoutRacer' , [ 'race' , 'hasTimedOut' ] ) ; } ) ; type methodSignatures = { [ key in keyof OpenYoloApi ] : typeof OpenYoloApiImpl . prototype [ key ] ; } ; param methodName param operationRequest param options param expectedResult function testOperationImpl < M extends keyof methodSignatures , Opt , Res > ( methodName , operationRequest , options , expectedResult ) { let dispatchSpy ; beforeEach ( ( ) => { dispatchSpy = spyOn ( operationRequest , 'dispatch' ) ; } ) ; it ( 'dispatches the request' , ( done ) => { dispatchSpy . and . returnValue ( Promise . resolve ( expectedResult ) ) ; ( openYoloApiImpl [ methodName ] as methodSignatures [ M ] ) ( options , timeoutRacerSpy ) . then ( ( result ) => { expect ( result ) . toBe ( expectedResult ) ; expect ( dispatchSpy ) . toHaveBeenCalledWith ( options , timeoutRacerSpy ) ; done ( ) ; } ) ; } ) ; it ( 'propagates error' , ( done ) => { timeoutRacerSpy . hasTimedOut . and . returnValue ( false ) ; dispatchSpy . and . returnValue ( Promise . reject ( otherError ) ) ; ( openYoloApiImpl [ methodName ] as methodSignatures [ M ] ) ( options , timeoutRacerSpy ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( otherError ) ; done ( ) ; } ) ; } ) ; it ( 'captures timeout and cancel operation' , ( done ) => { timeoutRacerSpy . hasTimedOut . and . returnValue ( true ) ; dispatchSpy . and . returnValue ( Promise . reject ( otherError ) ) ; spyOn ( CancelLastOperationRequest . prototype , 'dispatch' ) ; ( openYoloApiImpl [ methodName ] as methodSignatures [ M ] ) ( options , timeoutRacerSpy ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( otherError ) ; expect ( CancelLastOperationRequest . prototype . dispatch ) . toHaveBeenCalledWith ( undefined , undefined ) ; done ( ) ; } ) ; } ) ; it ( 'delegates to credential' , ( done ) => { timeoutRacerSpy . hasTimedOut . and . returnValue ( false ) ; dispatchSpy . and . returnValue ( Promise . reject ( wrapBrowserError ) ) ; navCredentialsSpy [ methodName ] . and . returnValue ( Promise . resolve ( expectedResult ) ) ; ( openYoloApiImpl [ methodName ] as methodSignatures [ M ] ) ( options , timeoutRacerSpy ) . then ( ( result ) => { expect ( result ) . toBe ( expectedResult ) ; expect ( navCredentialsSpy [ methodName ] ) . toHaveBeenCalledWith ( options ) ; done ( ) ; } ) ; } ) ; } describe ( 'hintsAvailable' , ( ) => { const options = { supportedAuthMethods : [ 'https://accounts.google.com' ] } ; testOperationImpl ( 'hintsAvailable' , HintAvailableRequest . prototype , options , true ) ; } ) ; describe ( 'hint' , ( ) => { const options = { supportedAuthMethods : [ 'https://accounts.google.com' ] } ; testOperationImpl ( 'hint' , HintRequest . prototype , options , credential ) ; } ) ; describe ( 'retrieve' , ( ) => { const options = { supportedAuthMethods : [ 'https://accounts.google.com' ] } ; testOperationImpl ( 'retrieve' , CredentialRequest . prototype , options , credential ) ; } ) ; describe ( 'save' , ( ) => { testOperationImpl ( 'save' , CredentialSave . prototype , credential , undefined ) ; } ) ; describe ( 'proxyLogin' , ( ) => { testOperationImpl ( 'proxyLogin' , ProxyLogin . prototype , credential , undefined ) ; } ) ; describe ( 'cancelLastOperation' , ( ) => { let dispatchSpy ; beforeEach ( ( ) => { dispatchSpy = spyOn ( CancelLastOperationRequest . prototype , 'dispatch' ) ; } ) ; it ( 'dispatches the request' , ( done ) => { dispatchSpy . and . returnValue ( Promise . resolve ( ) ) ; openYoloApiImpl . cancelLastOperation ( timeoutRacerSpy ) . then ( ( ) => { expect ( dispatchSpy ) . toHaveBeenCalledWith ( undefined , timeoutRacerSpy ) ; done ( ) ; } ) ; } ) ; it ( 'propagates the error' , ( done ) => { dispatchSpy . and . returnValue ( Promise . reject ( otherError ) ) ; openYoloApiImpl . cancelLastOperation ( timeoutRacerSpy ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( otherError ) ; done ( ) ; } ) ; } ) ; it ( 'delegates to credential' , ( done ) => { dispatchSpy . and . returnValue ( Promise . reject ( wrapBrowserError ) ) ; navCredentialsSpy . cancelLastOperation . and . returnValue ( Promise . resolve ( ) ) ; openYoloApiImpl . cancelLastOperation ( timeoutRacerSpy ) . then ( ( ) => { expect ( navCredentialsSpy . cancelLastOperation ) . toHaveBeenCalled ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( 'disableAutoSignIn' , ( ) => { it ( 'dispatches the request and calls navigator.credentials' , ( done ) => { spyOn ( DisableAutoSignIn . prototype , 'dispatch' ) . and . returnValue ( Promise . resolve ( ) ) ; navCredentialsSpy . disableAutoSignIn . and . returnValue ( Promise . resolve ( ) ) ; openYoloApiImpl . disableAutoSignIn ( timeoutRacerSpy ) . then ( ( ) => { expect ( DisableAutoSignIn . prototype . dispatch ) . toHaveBeenCalledWith ( undefined , timeoutRacerSpy ) ; expect ( navCredentialsSpy . disableAutoSignIn ) . toHaveBeenCalled ( ) ; done ( ) ; } ) ; } ) ; it ( 'captures timeout and cancel operation' , ( done ) => { timeoutRacerSpy . hasTimedOut . and . returnValue ( true ) ; spyOn ( DisableAutoSignIn . prototype , 'dispatch' ) . and . returnValue ( Promise . reject ( otherError ) ) ; spyOn ( CancelLastOperationRequest . prototype , 'dispatch' ) ; openYoloApiImpl . disableAutoSignIn ( timeoutRacerSpy ) . then ( ( ) => { done . fail ( 'Should not resolve!' ) ; } , ( error ) => { expect ( error ) . toBe ( otherError ) ; expect ( CancelLastOperationRequest . prototype . dispatch ) . toHaveBeenCalledWith ( undefined , undefined ) ; done ( ) ; } ) ; } ) ; } ) ; } ) ; describe ( 'FakeOpenYoloApi' , ( ) => { const expectedError = OpenYoloInternalError . unsupportedBrowser ( ) . toExposedError ( ) ; let openYoloApiImpl ; beforeEach ( ( ) => { openYoloApiImpl = new FakeOpenYoloApi ( ) ; } ) ; it ( 'rejects hintsAvailable' , ( done ) => { openYoloApiImpl . hintsAvailable ( { supportedAuthMethods : [ ] } ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects hint' , ( done ) => { openYoloApiImpl . hint ( { supportedAuthMethods : [ ] } ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects retrieve' , ( done ) => { openYoloApiImpl . retrieve ( { supportedAuthMethods : [ ] } ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects save' , ( done ) => { openYoloApiImpl . save ( credential ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects disableAutoSignIn' , ( done ) => { openYoloApiImpl . disableAutoSignIn ( ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects cancelLastOperation' , ( done ) => { openYoloApiImpl . cancelLastOperation ( ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; it ( 'rejects proxyLogin' , ( done ) => { openYoloApiImpl . proxyLogin ( credential ) . then ( ( ) => { done . fail ( ) ; } , ( error ) => { expect ( error ) . toEqual ( expectedError ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( 'isCompatibleBrowser' , ( ) => { it ( 'crypto available' , ( ) => { const windowWithCrypto = { crypto : { getRandomValues : ( ) => { } , subtle : { } } } ; expect ( isCompatibleBrowser ( windowWithCrypto as any ) ) . toBe ( true ) ; } ) ; it ( 'getRandomValues not available' , ( ) => { const windowWithCrypto = { crypto : { subtle : { } } } ; expect ( isCompatibleBrowser ( windowWithCrypto as any ) ) . toBe ( false ) ; } ) ; it ( 'subtle not available' , ( ) => { const windowWithCrypto = { crypto : { getRandomValues : ( ) => { } } } ; expect ( isCompatibleBrowser ( windowWithCrypto as any ) ) . toBe ( false ) ; } ) ; it ( 'subtle not available, webkitSubtle available' , ( ) => { const windowWithCrypto = { crypto : { getRandomValues : ( ) => { } , webkitSubtle : { } } } ; expect ( isCompatibleBrowser ( windowWithCrypto as any ) ) . toBe ( true ) ; } ) ; } ) ; } ) ;