summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorErik Larkö <erik@mullvad.net>2017-06-30 10:14:36 +0200
committerErik Larkö <erik@mullvad.net>2017-07-03 15:26:08 +0200
commitd06281eb566330a38b13a56bb9fdadbd7ef9aa99 (patch)
treee1bc465e4e1b54b9ccbb8c500ad9b921fd2e16f9 /test
parent005a6094aa48738f54e97d809de6e11f3b966da0 (diff)
downloadmullvadvpn-d06281eb566330a38b13a56bb9fdadbd7ef9aa99.tar.xz
mullvadvpn-d06281eb566330a38b13a56bb9fdadbd7ef9aa99.zip
Add first login workflow test
Diffstat (limited to 'test')
-rw-r--r--test/helpers/IpcChain.js84
-rw-r--r--test/helpers/ipc-helpers.js51
-rw-r--r--test/login.spec.js30
-rw-r--r--test/routing.spec.js2
4 files changed, 166 insertions, 1 deletions
diff --git a/test/helpers/IpcChain.js b/test/helpers/IpcChain.js
new file mode 100644
index 0000000000..247ea0526b
--- /dev/null
+++ b/test/helpers/IpcChain.js
@@ -0,0 +1,84 @@
+// @flow
+
+import { expect } from 'chai';
+import { check, failFast } from './ipc-helpers';
+
+export class IpcChain {
+ _expectedCalls: Array<string>;
+ _recordedCalls: Array<string>;
+ _mockIpc: {};
+ _done: (*) => void;
+
+ constructor(mockIpc: {}, done: (*) => void) {
+ this._expectedCalls = [];
+ this._recordedCalls = [];
+ this._mockIpc = mockIpc;
+ this._done = done;
+ }
+
+ addRequiredStep(ipcCall: string): StepBuilder {
+ this._expectedCalls.push(ipcCall);
+ return new StepBuilder(ipcCall, this._addStep.bind(this));
+ }
+
+ _addStep(step: StepBuilder) {
+ const me = this;
+ this._mockIpc[step.ipcCall] = function() {
+ return new Promise(r => me._stepPromiseCallback(step, r, arguments));
+ };
+ }
+
+ _stepPromiseCallback(step, resolve, args) {
+ this._registerCall(step.ipcCall);
+
+ if (step.inputValidation) {
+ failFast(() => step.inputValidation(...args), this._done);
+ }
+
+ if (this._isLastCall()) {
+ this._ensureChainCalledCorrectly();
+ }
+
+ resolve(step.returnValue);
+ }
+
+ _isLastCall(): boolean {
+ return this._recordedCalls.length === this._expectedCalls.length;
+ }
+
+ _ensureChainCalledCorrectly() {
+ check(() => {
+ expect(this._expectedCalls).to.deep.equal(this._recordedCalls);
+ }, this._done);
+ }
+
+ _registerCall(ipcCall: string) {
+ this._recordedCalls.push(ipcCall);
+ }
+}
+
+class StepBuilder {
+ ipcCall: string;
+ inputValidation: () => void;
+ returnValue: *;
+ _cb: (StepBuilder) => void;
+
+ constructor(ipcCall: string, cb: (StepBuilder)=> void) {
+ this.ipcCall = ipcCall;
+ this._cb = cb;
+ }
+
+ withInputValidation(iv: () => void): StepBuilder {
+ this.inputValidation = iv;
+ return this;
+ }
+
+ withReturnValue(rv: *): StepBuilder {
+ this.returnValue = rv;
+ return this;
+ }
+
+ done() {
+ this._cb(this);
+ }
+}
diff --git a/test/helpers/ipc-helpers.js b/test/helpers/ipc-helpers.js
new file mode 100644
index 0000000000..39fc9fba06
--- /dev/null
+++ b/test/helpers/ipc-helpers.js
@@ -0,0 +1,51 @@
+// @flow
+
+import { Backend } from '../../app/lib/backend';
+import { newMockIpc } from '../mocks/ipc';
+import configureStore from '../../app/redux/store';
+import { createMemoryHistory } from 'history';
+
+type DoneCallback = (?mixed) => void;
+type Check = () => void;
+
+// Mock localStorage because redux-localstorage has no test helpers
+// We use redux-localstorage when we setup the redux store to have the
+// store persist when the application is shut down.
+global.localStorage = {getItem: ()=>'{}', setItem: ()=>{}};
+
+export function setupBackendAndStore() {
+
+ const memoryHistory = createMemoryHistory();
+ const store = configureStore(null, memoryHistory);
+
+ const mockIpc = newMockIpc();
+
+ const backend = new Backend(store, mockIpc);
+
+ return { store, mockIpc, backend };
+}
+
+// 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);
+ }
+}
+
+
+// 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) {
+ try {
+ fn();
+ } catch(e) {
+ done(e);
+ }
+}
+
diff --git a/test/login.spec.js b/test/login.spec.js
new file mode 100644
index 0000000000..84ca0f431a
--- /dev/null
+++ b/test/login.spec.js
@@ -0,0 +1,30 @@
+// @flow
+
+import { expect } from 'chai';
+import { setupBackendAndStore } 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, done);
+ chain.addRequiredStep('getAccountData')
+ .withInputValidation((an) => {
+ expect(an).to.equal('123');
+ })
+ .done();
+
+ chain.addRequiredStep('setAccount')
+ .withInputValidation((an) => {
+ expect(an).to.equal('123');
+ })
+ .done();
+
+ const action: any = accountActions.login(backend, '123');
+ store.dispatch(action);
+ });
+});
+
diff --git a/test/routing.spec.js b/test/routing.spec.js
index 3e1f80be3c..ccb702de8b 100644
--- a/test/routing.spec.js
+++ b/test/routing.spec.js
@@ -24,7 +24,7 @@ describe('routing', function() {
});
const store = mockStore(state);
- const backend = new Backend(newMockIpc());
+ const backend = new Backend(store, newMockIpc());
mapBackendEventsToRouter(backend, store);
store.dispatch(accountActions.logout(backend));