diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/auth.spec.js | 41 | ||||
| -rw-r--r-- | test/autologin.spec.js | 126 | ||||
| -rw-r--r-- | test/components/Login.spec.js | 27 | ||||
| -rw-r--r-- | test/connect.spec.js | 62 | ||||
| -rw-r--r-- | test/helpers/IpcChain.js | 100 | ||||
| -rw-r--r-- | test/helpers/ipc-helpers.js | 94 | ||||
| -rw-r--r-- | test/login.spec.js | 83 | ||||
| -rw-r--r-- | test/logout.spec.js | 67 | ||||
| -rw-r--r-- | test/mocks/redux.js | 4 | ||||
| -rw-r--r-- | test/mocks/rpc.js (renamed from test/mocks/ipc.js) | 37 |
10 files changed, 34 insertions, 607 deletions
diff --git a/test/auth.spec.js b/test/auth.spec.js deleted file mode 100644 index 4f919dff87..0000000000 --- a/test/auth.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -// @flow - -import { setupIpcAndStore, setupBackendAndStore } from './helpers/ipc-helpers'; -import { IpcChain } from './helpers/IpcChain'; -import { Backend } from '../app/lib/backend'; - -describe('authentication', () => { - it('authenticates before ipc call if unauthenticated', (done) => { - const { store, mockIpc } = setupIpcAndStore(); - - const credentials = { - connectionString: 'ws://localhost:1234/', - sharedSecret: '1234', - }; - - const chain = new IpcChain(mockIpc); - chain.expect('authenticate').withInputValidation((secret) => { - expect(secret).to.equal(credentials.sharedSecret); - }); - chain.expect('connect'); - chain.end(done); - - const backend = new Backend(store, mockIpc); - backend.connect(credentials); - - backend.connectTunnel(); - }); - - it('reauthenticates on reconnect', async () => { - const { mockIpc, backend } = setupBackendAndStore(); - - mockIpc.authenticate = spy(mockIpc.authenticate); - await mockIpc.connectTunnel(); - mockIpc.killWebSocket(); - - expect(mockIpc.authenticate).to.not.have.been.called(); - - await backend.connectTunnel(); - expect(mockIpc.authenticate).to.have.been.called.once; - }); -}); diff --git a/test/autologin.spec.js b/test/autologin.spec.js deleted file mode 100644 index 505ad285c0..0000000000 --- a/test/autologin.spec.js +++ /dev/null @@ -1,126 +0,0 @@ -// @flow - -import { setupBackendAndStore, setupBackendAndMockStore, getLocation } from './helpers/ipc-helpers'; -import { IpcChain } from './helpers/IpcChain'; - -describe('autologin', () => { - it('should send get_account then get_account_data if an account is set', (done) => { - const { mockIpc, backend } = setupBackendAndStore(); - - const randomAccountToken = '12345'; - - const chain = new IpcChain(mockIpc); - chain.expect('getAccount').withReturnValue(randomAccountToken); - - chain.expect('getAccountData').withInputValidation((num) => { - expect(num).to.equal(randomAccountToken); - }); - - chain.end(done); - - backend.autologin(); - }); - - it('should redirect to the login page if no account is set', () => { - const { store, backend, mockIpc } = setupBackendAndMockStore(); - - mockIpc.getAccount = () => new Promise((_, reject) => reject('NO_ACCOUNT')); - - return backend - .autologin() - .then(() => { - expect(getLocation(store)).to.equal('/'); - }) - .catch((e) => { - if (e !== 'NO_ACCOUNT') { - throw e; - } - }); - }); - - it('should redirect to the login page for non-existing accounts', () => { - const { store, backend, mockIpc } = setupBackendAndMockStore(); - - mockIpc.getAccount = () => new Promise((r) => r('123')); - mockIpc.getAccountData = () => new Promise((_, reject) => reject('NO_ACCOUNT')); - - return backend - .autologin() - .then(() => { - expect(getLocation(store)).to.equal('/'); - }) - .catch((e) => { - if (e !== 'NO_ACCOUNT') { - throw e; - } - }); - }); - - it('should mark the state as not logged in if no account is set', () => { - const { store, backend, mockIpc } = setupBackendAndStore(); - - mockIpc.getAccount = () => Promise.resolve(null); - - return backend - .autologin() - .catch(() => {}) // ignore errors - .then(() => { - const state = store.getState().account; - - expect(state.status).to.equal('none'); - expect(state.accountToken).to.be.null; - expect(state.error).to.be.null; - }); - }); - - it('should mark the state as not logged in for non-existing accounts', () => { - const { store, backend, mockIpc } = setupBackendAndStore(); - - mockIpc.getAccount = () => new Promise((r) => r('123')); - mockIpc.getAccountData = () => new Promise((_, reject) => reject('NO ACCOUNT')); - - return backend - .autologin() - .catch(() => {}) // ignore errors - .then(() => { - const state = store.getState().account; - - expect(state.status).to.equal('none'); - expect(state.error).to.be.null; - }); - }); - - it('should put the account data in the state for existing accounts', () => { - const { store, backend, mockIpc } = setupBackendAndStore(); - mockIpc.getAccount = () => new Promise((r) => r('123')); - mockIpc.getAccountData = () => - new Promise((r) => - r({ - expiry: '2001-01-01T00:00:00Z', - }), - ); - - return backend.autologin().then(() => { - const state = store.getState().account; - expect(state.status).to.equal('ok'); - expect(state.accountToken).to.equal('123'); - expect(state.expiry).to.equal('2001-01-01T00:00:00Z'); - }); - }); - - it('should redirect to /connect for existing accounts', () => { - const { store, backend, mockIpc } = setupBackendAndMockStore(); - - mockIpc.getAccount = () => new Promise((r) => r('123')); - mockIpc.getAccountData = () => - new Promise((r) => - r({ - expiry: '2001-01-01T00:00:00Z', - }), - ); - - return backend.autologin().then(() => { - expect(getLocation(store)).to.equal('/connect'); - }); - }); -}); diff --git a/test/components/Login.spec.js b/test/components/Login.spec.js index f3cbc24de1..6a77313f5d 100644 --- a/test/components/Login.spec.js +++ b/test/components/Login.spec.js @@ -5,12 +5,14 @@ import { shallow } from 'enzyme'; import Login from '../../app/components/Login'; describe('components/Login', () => { - it('does not show the footer when logging in', () => { const component = shallow( - <Login { ...{ ...defaultProps, - loginState: 'logging in' - }} /> + <Login + {...{ + ...defaultProps, + loginState: 'logging in', + }} + />, ); const visibleFooters = getComponent(component, 'footerVisibility true'); const invisibleFooters = getComponent(component, 'footerVisibility false'); @@ -19,9 +21,7 @@ describe('components/Login', () => { }); it('shows the footer and account input when not logged in', () => { - const component = shallow( - <Login {...defaultProps} /> - ); + const component = shallow(<Login {...defaultProps} />); const visibleFooters = getComponent(component, 'footerVisibility true'); const invisibleFooters = getComponent(component, 'footerVisibility false'); expect(visibleFooters.length).to.equal(1); @@ -31,9 +31,12 @@ describe('components/Login', () => { it('does not show the footer nor account input when logged in', () => { const component = shallow( - <Login {...{ ...defaultProps, - loginState: 'ok' - }} /> + <Login + {...{ + ...defaultProps, + loginState: 'ok', + }} + />, ); const visibleFooters = getComponent(component, 'footerVisibility true'); const invisibleFooters = getComponent(component, 'footerVisibility false'); @@ -43,9 +46,7 @@ describe('components/Login', () => { }); it('logs in with the entered account number when clicking the login icon', (done) => { - const component = shallow( - <Login {...defaultProps } /> - ); + const component = shallow(<Login {...defaultProps} />); component.setProps({ accountToken: '12345', login: (accountToken) => { diff --git a/test/connect.spec.js b/test/connect.spec.js deleted file mode 100644 index 409a04c0fb..0000000000 --- a/test/connect.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -// @flow - -import connectionActions from '../app/redux/connection/actions'; -import { setupBackendAndStore, checkNextTick } from './helpers/ipc-helpers'; - -describe('connect', () => { - it("should set the connection state to 'disconnected' on failed attempts", (done) => { - const { store, mockIpc, backend } = setupBackendAndStore(); - - mockIpc.connectTunnel = () => new Promise((_, reject) => reject('Some error')); - - store.dispatch(connectionActions.connected()); - - expect(store.getState().connection.status).not.to.equal('disconnected'); - - store.dispatch(connectionActions.connect(backend)); - - checkNextTick(() => { - expect(store.getState().connection.status).to.equal('disconnected'); - }, done); - }); - - it('should update the state with the server address', () => { - const { store, backend } = setupBackendAndStore(); - - return backend.connectTunnel().then(() => { - const state = store.getState().connection; - expect(state.status).to.equal('connecting'); - }); - }); - - it("should correctly deduce 'connected' from backend states", (done) => { - const { store, mockIpc } = setupBackendAndStore(); - - checkNextTick(() => { - expect(store.getState().connection.status).not.to.equal('connected'); - mockIpc.sendNewState({ state: 'secured', target_state: 'secured' }); - expect(store.getState().connection.status).to.equal('connected'); - }, done); - }); - - it("should correctly deduce 'connecting' from backend states", (done) => { - const { store, mockIpc } = setupBackendAndStore(); - - checkNextTick(() => { - expect(store.getState().connection.status).not.to.equal('connecting'); - mockIpc.sendNewState({ state: 'unsecured', target_state: 'secured' }); - expect(store.getState().connection.status).to.equal('connecting'); - }, done); - }); - - it("should correctly deduce 'disconnected' from backend states", (done) => { - const { store, mockIpc } = setupBackendAndStore(); - store.dispatch(connectionActions.connected()); - - checkNextTick(() => { - expect(store.getState().connection.status).not.to.equal('disconnected'); - mockIpc.sendNewState({ state: 'unsecured', target_state: 'unsecured' }); - expect(store.getState().connection.status).to.equal('disconnected'); - }, done); - }); -}); diff --git a/test/helpers/IpcChain.js b/test/helpers/IpcChain.js deleted file mode 100644 index 547851fdcf..0000000000 --- a/test/helpers/IpcChain.js +++ /dev/null @@ -1,100 +0,0 @@ -// @flow - -export class IpcChain { - _expectedCalls: Array<string>; - _recordedCalls: Array<string>; - _mockIpc: {}; - _done: (?Error) => void; - _aborted: boolean; - - constructor(mockIpc: {}) { - this._expectedCalls = []; - this._recordedCalls = []; - this._mockIpc = mockIpc; - this._aborted = false; - } - - expect<R>(ipcCall: string): StepBuilder<R> { - const builder = new StepBuilder(ipcCall); - this._expectedCalls.push(ipcCall); - this._addStep(builder); - - return builder; - } - - _addStep<R>(step: StepBuilder<R>) { - this._mockIpc[step.ipcCall] = (...args: Array<mixed>) => { - return new Promise((r) => this._stepPromiseCallback(step, r, args)); - }; - } - - _stepPromiseCallback<R>(step: StepBuilder<R>, resolve: (?R) => void, args: Array<mixed>) { - if (this._aborted) { - return; - } - - this._registerCall(step.ipcCall); - - const inputValidation = step.inputValidation; - if (inputValidation) { - try { - inputValidation(...args); - } catch (error) { - this._abort(); - this._done(error); - return; - } - } - - if (this._isLastCall()) { - this._ensureChainCalledCorrectly(); - } - - resolve(step.returnValue); - } - - _abort() { - this._aborted = true; - } - - _isLastCall(): boolean { - return this._recordedCalls.length === this._expectedCalls.length; - } - - _ensureChainCalledCorrectly() { - try { - expect(this._expectedCalls).to.deep.equal(this._recordedCalls); - this._done(); - } catch (error) { - this._done(error); - } - } - - _registerCall(ipcCall: string) { - this._recordedCalls.push(ipcCall); - } - - end(done: (?Error) => void) { - this._done = done; - } -} - -class StepBuilder<R> { - ipcCall: string; - inputValidation: ?(...args: Array<mixed>) => void; - returnValue: ?R; - - constructor(ipcCall: string) { - this.ipcCall = ipcCall; - } - - withInputValidation(iv: (...args: Array<mixed>) => void): this { - this.inputValidation = iv; - return this; - } - - withReturnValue(rv: R): this { - this.returnValue = rv; - return this; - } -} diff --git a/test/helpers/ipc-helpers.js b/test/helpers/ipc-helpers.js deleted file mode 100644 index 5bfe1d0267..0000000000 --- a/test/helpers/ipc-helpers.js +++ /dev/null @@ -1,94 +0,0 @@ -// @flow - -import { Backend } from '../../app/lib/backend'; -import { newMockIpc } from '../mocks/ipc'; -import configureStore from '../../app/redux/store'; -import { createMemoryHistory } from 'history'; -import { mockStore } from '../mocks/redux'; - -type DoneCallback = (?Error) => void; -type Check = () => void; - -export function setupIpcAndStore() { - const memoryHistory = createMemoryHistory(); - const store = configureStore(null, memoryHistory); - const mockIpc = newMockIpc(); - - return { store, mockIpc }; -} - -export function setupBackendAndStore() { - const { store, mockIpc } = setupIpcAndStore(); - const backend = new Backend(store, mockIpc); - - return { store, mockIpc, backend }; -} - -export function setupBackendAndMockStore() { - const store = mockStore(_initialState()); - const mockIpc = newMockIpc(); - const backend = new Backend(store, mockIpc); - return { store, mockIpc, backend }; -} - -function _initialState() { - const { store } = setupIpcAndStore(); - return store.getState(); -} - -// chai and async aren't the best of friends. To allow us -// to get the assertion error in the output of failed async -// tests we need to do this try-catch thing. -export function check(fn: Check, done: DoneCallback) { - try { - fn(); - done(); - } catch (e) { - done(e); - } -} - -// Sometimes with redux we cannot know when all reducers have -// finished running. This function puts the check at the end -// of the execution queue, hopefully resulting in the check being -// run after the reducers are finished -export function checkNextTick(fn: Check, done: DoneCallback) { - setTimeout(() => { - check(fn, done); - }, 1); -} - -// In async tests where we want to test a chain of IPC messages -// we can only invoke `done` for the last message. This function -// is for the intermediate messages. -export function failFast(fn: Check, done: DoneCallback): boolean { - try { - fn(); - return false; - } catch (e) { - done(e); - return true; - } -} -export function failFastNextTick(fn: Check, done: DoneCallback) { - setTimeout(() => { - failFast(fn, done); - }, 1); -} - -type MockStore = { - getActions: () => Array<{ type: string, payload: Object }>, -}; -// Parses the action log to find out which URL we most recently navigated to -// Note that this cannot be done with the real redux store, but rather must be -// done with the mock store. -export function getLocation(store: MockStore): ?string { - const navigations = store - .getActions() - .filter((action) => action.type === '@@router/CALL_HISTORY_METHOD'); - if (navigations.length === 0) { - return null; - } - - return navigations[navigations.length - 1].payload.args[0]; -} diff --git a/test/login.spec.js b/test/login.spec.js deleted file mode 100644 index a2390b5241..0000000000 --- a/test/login.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -// @flow - -import { - setupBackendAndStore, - setupBackendAndMockStore, - checkNextTick, - getLocation, - failFast, - check, -} from './helpers/ipc-helpers'; -import { IpcChain } from './helpers/IpcChain'; -import accountActions from '../app/redux/account/actions'; - -describe('Logging in', () => { - it('should validate the account number and then set it in the backend', (done) => { - const { store, mockIpc, backend } = setupBackendAndStore(); - - const chain = new IpcChain(mockIpc); - chain.expect('getAccountData').withInputValidation((an) => { - expect(an).to.equal('123'); - }); - - chain.expect('setAccount').withInputValidation((an) => { - expect(an).to.equal('123'); - }); - - chain.end(done); - - store.dispatch(accountActions.login(backend, '123')); - }); - - it('should put the account data in the state', () => { - const { store, backend, mockIpc } = setupBackendAndStore(); - mockIpc.getAccountData = () => - new Promise((r) => - r({ - expiry: '2001-01-01T00:00:00Z', - }), - ); - - return backend.login('123').then(() => { - const state = store.getState().account; - expect(state.status).to.equal('ok'); - expect(state.accountToken).to.equal('123'); - expect(state.expiry).to.equal('2001-01-01T00:00:00Z'); - }); - }); - - it('should indicate failure for non-existing accounts', (done) => { - const { store, mockIpc, backend } = setupBackendAndStore(); - - mockIpc.getAccountData = (_num) => - new Promise((_, reject) => { - reject('NO SUCH ACCOUNT'); - }); - - store.dispatch(accountActions.login(backend, '123')); - - checkNextTick(() => { - const state = store.getState().account; - expect(state.status).to.equal('failed'); - expect(state.error).to.not.be.null; - }, done); - }); - - it('should redirect to /connect after 1s after successful login', (done) => { - const { store, backend } = setupBackendAndMockStore(); - - store.dispatch(accountActions.login(backend, '123')); - - setTimeout(() => { - failFast(() => { - expect(getLocation(store)).not.to.equal('/connect'); - }, done); - }, 100); - - setTimeout(() => { - check(() => { - expect(getLocation(store)).to.equal('/connect'); - }, done); - }, 1100); - }); -}); diff --git a/test/logout.spec.js b/test/logout.spec.js deleted file mode 100644 index c973c6a81a..0000000000 --- a/test/logout.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow - -import { - setupBackendAndStore, - setupBackendAndMockStore, - getLocation, - checkNextTick, - failFastNextTick, -} from './helpers/ipc-helpers'; -import { IpcChain } from './helpers/IpcChain'; -import accountActions from '../app/redux/account/actions'; - -describe('logging out', () => { - it('should set the account to null and then disconnect', (done) => { - const { mockIpc, backend } = setupBackendAndStore(); - - const chain = new IpcChain(mockIpc); - chain.expect('setAccount').withInputValidation((num) => { - expect(num).to.be.null; - }); - chain.expect('disconnect'); - chain.end(done); - - backend.logout(); - }); - - it('should remove the account number from the store', (done) => { - const { store, backend, mockIpc } = setupBackendAndStore(); - mockIpc.getAccountData = () => - new Promise((r) => - r({ - expiry: '2001-01-01T00:00:00.000Z', - }), - ); - const action: any = accountActions.login(backend, '123'); - store.dispatch(action); - - const expectedLogoutState = { - status: 'none', - accountToken: null, - expiry: null, - error: null, - }; - - failFastNextTick(() => { - let state = store.getState().account; - expect(state).not.to.include(expectedLogoutState); - - backend.logout(); - - checkNextTick(() => { - state = store.getState().account; - expect(state).to.include(expectedLogoutState); - }, done); - }, done); - }); - - it('should redirect to / on logout', (done) => { - const { store, backend } = setupBackendAndMockStore(); - - backend.logout(); - - checkNextTick(() => { - expect(getLocation(store)).to.equal('/'); - }, done); - }); -}); diff --git a/test/mocks/redux.js b/test/mocks/redux.js deleted file mode 100644 index bbab7b17f4..0000000000 --- a/test/mocks/redux.js +++ /dev/null @@ -1,4 +0,0 @@ -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -export const mockStore = configureMockStore([thunk]); diff --git a/test/mocks/ipc.js b/test/mocks/rpc.js index b610244462..4b37ca35aa 100644 --- a/test/mocks/ipc.js +++ b/test/mocks/rpc.js @@ -6,21 +6,20 @@ import type { BackendState, } from '../../app/lib/daemon-rpc'; -interface MockIpc { +interface MockRpc { sendNewState: (BackendState) => void; - killWebSocket: () => void; -getAccountData: (AccountToken) => Promise<AccountData>; -connectTunnel: () => Promise<void>; -getAccount: () => Promise<?AccountToken>; -authenticate: (string) => Promise<void>; } -export function newMockIpc() { +export function newMockRpc() { const stateListeners = []; - let connectionOpenListener: ?() => void; - let connectionCloseListener: ?(error: ?Error) => void; + const openListeners = []; + const closeListeners = []; - const mockIpc: DaemonRpcProtocol & MockIpc = { + const mockIpc: DaemonRpcProtocol & MockRpc = { setConnectionString: (_str: string) => {}, getAccountData: (accountToken) => Promise.resolve({ @@ -49,11 +48,15 @@ export function newMockIpc() { setAllowLan: (_allowLan: boolean) => Promise.resolve(), getAllowLan: () => Promise.resolve(true), connect: () => { - if (connectionOpenListener) { - connectionOpenListener(); + for (const listener of openListeners) { + listener(); + } + }, + disconnect: () => { + for (const listener of closeListeners) { + listener(); } }, - disconnect: () => {}, connectTunnel: () => Promise.resolve(), disconnectTunnel: () => Promise.resolve(), getLocation: () => @@ -80,20 +83,20 @@ export function newMockIpc() { } }, addOpenConnectionObserver: (listener: () => void) => { - connectionOpenListener = listener; + openListeners.push(listener); + return { + unsubscribe: () => {}, + }; }, addCloseConnectionObserver: (listener: (error: ?Error) => void) => { - connectionCloseListener = listener; + closeListeners.push(listener); + return { + unsubscribe: () => {}, + }; }, authenticate: (_secret: string) => Promise.resolve(), getAccountHistory: () => Promise.resolve([]), removeAccountFromHistory: (_accountToken) => Promise.resolve(), - - killWebSocket: () => { - if (connectionCloseListener) { - connectionCloseListener(); - } - }, }; return mockIpc; |
