Skip to content

Commit ef4c854

Browse files
committed
tests: add and update tests for auth
1 parent 357069f commit ef4c854

File tree

15 files changed

+252
-28
lines changed

15 files changed

+252
-28
lines changed

app/src/__tests__/App.spec.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import React from 'react';
22
import { render } from '@testing-library/react';
3+
import * as config from 'config';
34
import App from '../App';
45

56
describe('App Component', () => {
67
const renderApp = () => {
78
return render(<App />);
89
};
910

10-
it('should render the App', () => {
11-
const { getByText } = renderApp();
12-
const linkElement = getByText('Node Status');
13-
expect(linkElement).toBeInTheDocument();
11+
it('should render the App', async () => {
12+
// ensure init is called in the store so the UI is displayed
13+
Object.defineProperty(config, 'IS_TEST', { get: () => false });
14+
const { findByText } = renderApp();
15+
expect(await findByText('Shushtar')).toBeInTheDocument();
16+
expect(await findByText('logo.svg')).toBeInTheDocument();
17+
// revert IS_DEV
18+
Object.defineProperty(config, 'IS_TEST', { get: () => true });
1419
});
1520
});

app/src/__tests__/Pages.spec.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
import React from 'react';
22
import { renderWithProviders } from 'util/tests';
3+
import { createStore } from 'store';
34
import Pages from 'components/Pages';
45

56
describe('Pages Component', () => {
6-
const render = () => {
7-
return renderWithProviders(<Pages />);
7+
const render = async () => {
8+
const store = createStore();
9+
await store.init();
10+
return renderWithProviders(<Pages />, store);
811
};
912

10-
it('should display the Loop page by default', () => {
11-
const { getByText, store } = render();
13+
it('should display the Auth page by default', async () => {
14+
const { getByText, store } = await render();
15+
expect(getByText('Shushtar')).toBeInTheDocument();
16+
expect(store.uiStore.page).toBe('auth');
17+
});
18+
19+
it('should display the Loop page', async () => {
20+
const { getByText, store } = await render();
21+
store.uiStore.goToLoop();
1222
expect(getByText('Lightning Loop')).toBeInTheDocument();
1323
expect(store.uiStore.page).toBe('loop');
1424
});
1525

16-
it('should display the History page', () => {
17-
const { getByText, store } = render();
26+
it('should display the History page', async () => {
27+
const { getByText, store } = await render();
1828
store.uiStore.goToHistory();
1929
expect(getByText('Loop History')).toBeInTheDocument();
2030
expect(store.uiStore.page).toBe('history');
2131
});
2232

23-
it('should display the Settings page', () => {
24-
const { getByText, store } = render();
33+
it('should display the Settings page', async () => {
34+
const { getByText, store } = await render();
2535
store.uiStore.goToSettings();
2636
expect(getByText('Settings')).toBeInTheDocument();
2737
expect(store.uiStore.page).toBe('settings');
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
import { grpc } from '@improbable-eng/grpc-web';
3+
import { fireEvent } from '@testing-library/react';
4+
import { renderWithProviders } from 'util/tests';
5+
import { createStore, Store } from 'store';
6+
import AuthPage from 'components/auth/AuthPage';
7+
8+
const grpcMock = grpc as jest.Mocked<typeof grpc>;
9+
10+
describe('AuthPage ', () => {
11+
let store: Store;
12+
13+
beforeEach(async () => {
14+
store = createStore();
15+
await store.init();
16+
});
17+
18+
const render = () => {
19+
return renderWithProviders(<AuthPage />, store);
20+
};
21+
22+
it('should display the title', () => {
23+
const { getByText } = render();
24+
expect(getByText('Shushtar')).toBeInTheDocument();
25+
});
26+
27+
it('should display the password field', () => {
28+
const { getByLabelText } = render();
29+
expect(getByLabelText('Enter your password in the field above')).toBeInTheDocument();
30+
});
31+
32+
it('should display the submit button', () => {
33+
const { getByText } = render();
34+
expect(getByText('Submit')).toBeInTheDocument();
35+
});
36+
37+
it('should display nothing when the store is not initialized', () => {
38+
const { getByText, queryByText } = render();
39+
expect(getByText('Shushtar')).toBeInTheDocument();
40+
store.initialized = false;
41+
expect(queryByText('Shushtar')).not.toBeInTheDocument();
42+
});
43+
44+
it('should display an error when submitting an empty password', async () => {
45+
const { getByText, findByText } = render();
46+
fireEvent.click(getByText('Submit'));
47+
expect(await findByText('oops, password is required')).toBeInTheDocument();
48+
});
49+
50+
it('should display an error when submitting an invalid password', async () => {
51+
grpcMock.unary.mockImplementationOnce(desc => {
52+
if (desc.methodName === 'GetInfo') throw new Error('test-err');
53+
return undefined as any;
54+
});
55+
56+
const { getByText, getByLabelText, findByText } = render();
57+
const input = getByLabelText('Enter your password in the field above');
58+
fireEvent.change(input, { target: { value: 'test-pw' } });
59+
fireEvent.click(getByText('Submit'));
60+
expect(await findByText('oops, that password is incorrect')).toBeInTheDocument();
61+
});
62+
});

app/src/__tests__/components/layout/Layout.spec.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import React from 'react';
22
import { fireEvent } from '@testing-library/react';
33
import { renderWithProviders } from 'util/tests';
4+
import { createStore } from 'store';
45
import Layout from 'components/layout/Layout';
56

67
describe('Layout component', () => {
78
const render = () => {
8-
return renderWithProviders(<Layout />);
9+
const store = createStore();
10+
store.uiStore.page = 'loop';
11+
return renderWithProviders(<Layout />, store);
912
};
1013

1114
it('should display the hamburger menu', () => {
@@ -41,7 +44,7 @@ describe('Layout component', () => {
4144
expect(getByText('Lightning Loop').parentElement).toHaveClass('active');
4245
});
4346

44-
it('should navigate back to the Settings page', () => {
47+
it('should navigate to the Settings page', () => {
4548
const { getByText, store } = render();
4649
expect(store.uiStore.page).toBe('loop');
4750
fireEvent.click(getByText('Settings'));
@@ -51,4 +54,11 @@ describe('Layout component', () => {
5154
expect(store.uiStore.page).toBe('loop');
5255
expect(getByText('Lightning Loop').parentElement).toHaveClass('active');
5356
});
57+
58+
it('should not display the sidebar on the auth page', () => {
59+
const { getByText, queryByText, store } = render();
60+
expect(getByText('menu.svg')).toBeInTheDocument();
61+
store.uiStore.page = 'auth';
62+
expect(queryByText('menu.svg')).not.toBeInTheDocument();
63+
});
5464
});

app/src/__tests__/components/loop/LoopHistory.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('LoopHistory component', () => {
1010

1111
beforeEach(async () => {
1212
store = createStore();
13-
await store.init();
13+
await store.fetchAllData();
1414

1515
// remove all but one swap to prevent `getByText` from
1616
// complaining about multiple elements in tests

app/src/__tests__/components/loop/LoopPage.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('LoopPage component', () => {
1616

1717
beforeEach(async () => {
1818
store = createStore();
19-
await store.init();
19+
await store.fetchAllData();
2020
});
2121

2222
const render = () => {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { grpc } from '@improbable-eng/grpc-web';
2+
import AppStorage from 'util/appStorage';
3+
import { AuthStore, createStore, Store } from 'store';
4+
import { PersistentSettings } from 'store/stores/settingsStore';
5+
6+
const grpcMock = grpc as jest.Mocked<typeof grpc>;
7+
const appStorageMock = AppStorage as jest.Mock<AppStorage<PersistentSettings>>;
8+
9+
describe('AuthStore', () => {
10+
let rootStore: Store;
11+
let store: AuthStore;
12+
13+
beforeEach(() => {
14+
rootStore = createStore();
15+
store = rootStore.authStore;
16+
});
17+
18+
it('should set credentials', () => {
19+
expect(store.credentials).toBe('');
20+
store.setCredentials('test');
21+
expect(store.credentials).toEqual('test');
22+
store.setCredentials('');
23+
expect(store.credentials).toEqual('');
24+
});
25+
26+
it('should login successfully', async () => {
27+
await store.login('test-pw');
28+
expect(store.credentials).toBe('dGVzdC1wdzp0ZXN0LXB3');
29+
});
30+
31+
it('should fail to login with a blank password', async () => {
32+
await expect(store.login('')).rejects.toThrow('oops, password is required');
33+
expect(store.credentials).toBe('');
34+
});
35+
36+
it('should fail to login with an invalid password', async () => {
37+
grpcMock.unary.mockImplementationOnce(desc => {
38+
if (desc.methodName === 'GetInfo') throw new Error('test-err');
39+
return undefined as any;
40+
});
41+
await expect(store.login('test-pw')).rejects.toThrow(
42+
'oops, that password is incorrect',
43+
);
44+
expect(store.credentials).toBe('');
45+
});
46+
47+
it('should load credentials from session storage', async () => {
48+
const getMock = appStorageMock.mock.instances[0].getSession as jest.Mock;
49+
getMock.mockReturnValue('test-creds');
50+
await store.init();
51+
expect(store.credentials).toBe('test-creds');
52+
});
53+
54+
it('should not store invalid credentials from session storage', async () => {
55+
grpcMock.unary.mockImplementationOnce((desc, opts) => {
56+
if (desc.methodName === 'GetInfo') {
57+
opts.onEnd({
58+
status: grpc.Code.Unauthenticated,
59+
} as any);
60+
}
61+
return undefined as any;
62+
});
63+
const getMock = appStorageMock.mock.instances[0].getSession as jest.Mock;
64+
getMock.mockReturnValue('test-creds');
65+
await store.init();
66+
expect(store.credentials).toBe('');
67+
});
68+
});

app/src/__tests__/store/buildSwapStore.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('BuildSwapStore', () => {
1515

1616
beforeEach(async () => {
1717
rootStore = createStore();
18-
await rootStore.init();
18+
await rootStore.fetchAllData();
1919
store = rootStore.buildSwapStore;
2020
});
2121

app/src/__tests__/store/swapStore.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('SwapStore', () => {
2323
expect(store.sortedSwaps).toHaveLength(7);
2424
});
2525

26-
it('should handle errors fetching channels', async () => {
26+
it('should handle errors fetching swaps', async () => {
2727
grpcMock.unary.mockImplementationOnce(desc => {
2828
if (desc.methodName === 'ListSwaps') throw new Error('test-err');
2929
return undefined as any;
@@ -108,4 +108,25 @@ describe('SwapStore', () => {
108108
store.stopPolling();
109109
expect(store.pollingInterval).toBeUndefined();
110110
});
111+
112+
it('should stop polling if there is an error fetching swaps', async () => {
113+
await store.fetchSwaps();
114+
const swap = store.sortedSwaps[0];
115+
// create a pending swap to trigger auto-polling
116+
swap.state = LOOP.SwapState.INITIATED;
117+
expect(store.pendingSwaps).toHaveLength(1);
118+
// wait for polling to start
119+
await waitFor(() => {
120+
expect(store.pollingInterval).toBeDefined();
121+
});
122+
// induce a failure when fetching swaps
123+
grpcMock.unary.mockImplementationOnce(desc => {
124+
if (desc.methodName === 'ListSwaps') throw new Error('test-err');
125+
return undefined as any;
126+
});
127+
// confirm polling has stopped
128+
await waitFor(() => {
129+
expect(store.pollingInterval).toBeUndefined();
130+
});
131+
});
111132
});

app/src/__tests__/store/uiStore.spec.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { values } from 'mobx';
2-
import { createStore, UiStore } from 'store';
2+
import { AuthenticationError } from 'util/errors';
3+
import { createStore, Store, UiStore } from 'store';
34

45
describe('UiStore', () => {
6+
let rootStore: Store;
57
let store: UiStore;
68

79
beforeEach(() => {
8-
store = createStore().uiStore;
10+
rootStore = createStore();
11+
store = rootStore.uiStore;
912
});
1013

1114
it('should add an alert', async () => {
@@ -26,4 +29,17 @@ describe('UiStore', () => {
2629
store.clearAlert(alert.id);
2730
expect(store.alerts.size).toBe(0);
2831
});
32+
33+
it('should handle errors', () => {
34+
store.handleError(new Error('message'), 'title');
35+
expect(store.alerts.size).toBe(1);
36+
});
37+
38+
it('should handle authentication errors', () => {
39+
rootStore.authStore.authenticated = true;
40+
expect(store.alerts.size).toBe(0);
41+
store.handleError(new AuthenticationError());
42+
expect(rootStore.authStore.authenticated).toBe(false);
43+
expect(store.alerts.size).toBe(1);
44+
});
2945
});

0 commit comments

Comments
 (0)