Skip to content

Commit 4cea6bf

Browse files
committed
Server-side loading of TC user profile + misc fixes
1 parent f3d273d commit 4cea6bf

16 files changed

+198
-56
lines changed

.babelrc

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@
66
"plugins": [
77
"inline-react-svg",
88
["module-resolver", {
9-
"alias": {
10-
// NOTE: Some aliases related to assets and styles are defined in
11-
// webpack config.
12-
"actions": "./actions",
13-
"components": "./components",
14-
"containers": "./containers",
15-
"reducers": "./reducers",
16-
"routes": "./routes",
17-
"server": "./server",
18-
"services": "./services",
19-
"utils": "./utils"
20-
},
219
"extensions": [".js", ".jsx"],
2210
"root": [
2311
"./src/shared",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Mock redux-devtools-dock-monitor module.
3+
* Allows to test development-only code depending on that module even
4+
* in production environment, where that module is not installed.
5+
*/
6+
7+
export default function ReduxDevtoolsDockMonitor() {
8+
return null;
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ReduxDevtoolsLogMonitor() {
2+
return null;
3+
}

__mocks__/redux-devtools.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import _ from 'lodash';
2+
3+
export function createDevTools(obj) {
4+
const res = () => obj;
5+
res.instrument = _.noop;
6+
return res;
7+
}
8+
9+
export default undefined;

__mocks__/webpack-dev-middleware.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function webpackDevMiddleware(req, res, next) {
2+
if (next) next();
3+
}
4+
5+
module.exports = () => webpackDevMiddleware;

__mocks__/webpack-hot-middleware.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function webpackHotMiddleware(req, res, next) {
2+
if (next) next();
3+
}
4+
5+
module.exports = () => webpackHotMiddleware;

__mocks__/webpack.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import _ from 'lodash';
2+
3+
module.exports = _.noop;

__tests__/client/index.jsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ jest.setMock(`${SRC}/shared/actions/auth`, mockAuthActions);
106106
const mockStoreFactory = jest.fn(() => Promise.resolve({
107107
dispatch: _.noop,
108108
getState: () => ({
109-
auth: {},
109+
auth: {
110+
tokenV2: '12345',
111+
tokenV3: '12345',
112+
},
110113
}),
111114
}));
112115
jest.setMock(`${SRC}/shared/store-factory`, mockStoreFactory);
113116

117+
/* Some other mocks */
118+
114119
jest.setMock(`${SRC}/shared`, {
115120
default: () => <div>Application</div>,
116121
});
@@ -173,6 +178,23 @@ describe('Properly starts with process.env.FRONT_ENV evaluating true', () => {
173178
}),
174179
);
175180

181+
test('Does not write auth tokens to the state, when no need to', () =>
182+
new Promise((resolve) => {
183+
tokenV2 = '12345';
184+
tokenV3 = '12345';
185+
require(MODULE);
186+
187+
/* NOTE: We have mocked getFreshToken to return Promise.resolve(..),
188+
* which resolves immediately. Thus, this call to setImmediate(..) is
189+
* enough to wait until tokens are processed. */
190+
setImmediate(() => {
191+
expect(mockAuthActions.auth.setTcTokenV2).not.toHaveBeenCalled();
192+
expect(mockAuthActions.auth.setTcTokenV3).not.toHaveBeenCalled();
193+
resolve();
194+
});
195+
}),
196+
);
197+
176198
test('Clears auth cookies when authorization fails', () =>
177199
new Promise((done) => {
178200
mockTcAccounts.getFreshToken = () => Promise.reject('');

__tests__/server/server.js

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,16 @@ import _ from 'lodash';
22

33
const MODULE = require.resolve('server/server');
44

5-
jest.setMock('webpack', _.noop);
65
jest.setMock('../../config/webpack/development', {
76
output: {
87
publicPath: '',
98
},
109
});
11-
jest.setMock('../../src/server/renderer', _.noop);
12-
13-
/*
14-
jest.setMock('webpack-dev-middleware', () =>
15-
(req, res, next) => next && next(),
16-
);
17-
18-
jest.setMock('webpack-hot-middleware', () =>
19-
(req, res, next) => next && next(),
20-
);
21-
*/
10+
jest.setMock(require.resolve('server/renderer'), _.noop);
2211

2312
afterAll(() => {
13+
delete process.env.DEV_TOOLS;
2414
delete process.env.FRONT_END;
25-
process.env.NODE_ENV = 'test';
2615
});
2716

2817
beforeEach(() => {
@@ -39,8 +28,14 @@ test('Does not throw when executed at the back end', () => {
3928
expect(() => require(MODULE)).not.toThrow();
4029
});
4130

42-
test('Does not throw when executed at the back end in dev', () => {
43-
process.env.NODE_ENV = 'development';
31+
test('Does not throw when executed with pipe name', () => {
32+
process.env.PORT = 80;
33+
expect(() => require(MODULE)).not.toThrow();
34+
delete process.env.PORT;
35+
});
36+
37+
test('Does not throw when uses dev tools', () => {
38+
process.env.DEV_TOOLS = true;
4439
expect(() => require(MODULE)).not.toThrow();
45-
process.env.NODE_ENV = 'test';
40+
delete process.env.DEV_TOOLS;
4641
});

__tests__/shared/containers/DevTools.jsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,22 @@
22
* work in prod until we find a way to mock a non-installed module. I believe,
33
* Babel should be able to help here. */
44

5+
/*
56
import _ from 'lodash';
67
78
test('placeholder', _.noop);
9+
*/
10+
811

9-
/*
1012
import React from 'react';
1113
import Rnd from 'react-test-renderer/shallow';
12-
import { createStore } from 'redux';
1314

14-
jest.setMock('redux-devtools', {});
1515
const DevTools = require('containers/DevTools').default;
1616

1717
const rnd = new Rnd();
18-
const store = createStore(() => ({}), {}, DevTools.instrument());
18+
// const store = createStore(() => ({}), {}, DevTools.instrument());
1919

2020
test('Snapshot match', () => {
21-
rnd.render((
22-
<DevTools
23-
store={store}
24-
/>
25-
));
21+
rnd.render(<DevTools />);
2622
expect(rnd.getRenderOutput()).toMatchSnapshot();
2723
});
28-
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Snapshot match 1`] = `
4+
<ReduxDevtoolsDockMonitor
5+
changePositionKey="ctrl-p"
6+
toggleVisibilityKey="ctrl-m"
7+
>
8+
<ReduxDevtoolsLogMonitor
9+
theme="tomorrow"
10+
/>
11+
</ReduxDevtoolsDockMonitor>
12+
`;

__tests__/shared/reducers/auth.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { mockAction } from 'utils/mock';
2+
import { toFSA } from 'utils/redux';
3+
4+
const dummy = 'DUMMY';
5+
6+
const mockActions = {
7+
auth: {
8+
loadProfile: mockAction('LOAD_PROFILE', Promise.resolve('Profile')),
9+
setTcTokenV2: mockAction('SET_TC_TOKEN_V2', 'Token V2'),
10+
setTcTokenV3: mockAction('SET_TC_TOKEN_V3', 'Token V3'),
11+
},
12+
};
13+
jest.setMock(require.resolve('actions/auth'), mockActions);
14+
15+
jest.setMock('tc-accounts', {
16+
decodeToken: () => 'User object',
17+
});
18+
19+
const reducers = require('reducers/auth');
20+
21+
function testReducer(reducer, istate) {
22+
test('Initial state', () => {
23+
const state = reducer(undefined, {});
24+
expect(state).toEqual(istate);
25+
});
26+
27+
test('Load profile', () =>
28+
toFSA(mockActions.auth.loadProfile()).then((action) => {
29+
const state = reducer({ dummy }, action);
30+
expect(state).toEqual({
31+
dummy,
32+
profile: 'Profile',
33+
});
34+
}),
35+
);
36+
37+
test('Set TC Token V2', () => {
38+
const state = reducer({ dummy }, mockActions.auth.setTcTokenV2());
39+
expect(state).toEqual({
40+
dummy,
41+
tokenV2: 'Token V2',
42+
});
43+
});
44+
45+
test('Set TC Token V3', () => {
46+
const state = reducer({ dummy }, mockActions.auth.setTcTokenV3());
47+
expect(state).toEqual({
48+
dummy,
49+
tokenV3: 'Token V3',
50+
user: 'User object',
51+
});
52+
});
53+
54+
test('Set TC Token V3 with failure', () => {
55+
mockActions.auth.setTcTokenV3 = mockAction('SET_TC_TOKEN_V3', null);
56+
const state = reducer({ dummy }, mockActions.auth.setTcTokenV3());
57+
expect(state).toEqual({
58+
dummy,
59+
tokenV3: null,
60+
user: null,
61+
});
62+
mockActions.auth.setTcTokenV3 = mockAction('SET_TC_TOKEN_V3', 'Token V3');
63+
});
64+
}
65+
66+
describe('Default reducer', () => {
67+
testReducer(reducers.default, {});
68+
});
69+
70+
describe('Factory without server side rendering', () =>
71+
reducers.factory().then(res =>
72+
testReducer(res, {}),
73+
),
74+
);
75+
76+
describe('Factory with server side rendering', () =>
77+
reducers.factory({
78+
cookies: {
79+
tcjwt: 'Token V2',
80+
tctV3: 'Token V3',
81+
},
82+
}).then(res =>
83+
testReducer(res, {}),
84+
),
85+
);

__tests__/shared/store-factory.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const MODULE = require.resolve('shared/store-factory');
22

33
afterAll(() => {
4-
process.env.NODE_ENV = 'test';
4+
delete process.env.DEV_TOOLS;
55
});
66

77
beforeEach(() => {
@@ -12,9 +12,9 @@ test('Does not throw', () => {
1212
expect(() => require(MODULE)).not.toThrow();
1313
});
1414

15-
test('Does not throw in dev', () => {
16-
process.env.NODE_ENV = 'development';
15+
test('Does not throw when uses dev tools', () => {
16+
process.env.DEV_TOOLS = true;
1717
expect(() => require(MODULE)).not.toThrow();
18-
process.env.NODE_ENV = 'test';
18+
delete process.env.DEV_TOOLS;
1919
});
2020

config/jest/default.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"coverageDirectory": "__coverage__",
77
"coverageThreshold": {
88
"global": {
9-
"branches": 77.00,
10-
"functions": 88.50,
11-
"lines": 88.75,
12-
"statements": 87.50
9+
"branches": 80.00,
10+
"functions": 90.00,
11+
"lines": 90.00,
12+
"statements": 90.00
1313
}
1414
},
1515
"moduleNameMapper": {

src/client/index.jsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,9 @@ function authenticate(store) {
6262
}).then(({ tctV2, tctV3 }) => {
6363
const auth = store.getState().auth;
6464

65-
/* TODO: Once the server-side rendering supports proper loading of the
66-
* user profile, this dispatch should be moved inside the first if block.
67-
*/
68-
store.dispatch(actions.auth.loadProfile(tctV3));
69-
7065
if (auth.tokenV3 !== (tctV3 || null)) {
7166
store.dispatch(actions.auth.setTcTokenV3(tctV3));
67+
store.dispatch(actions.auth.loadProfile(tctV3));
7268
}
7369
if (auth.tokenV2 !== (tctV2 || null)) {
7470
store.dispatch(actions.auth.setTcTokenV2(tctV2));

src/shared/reducers/auth.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
import actions from 'actions/auth';
66
import { handleActions } from 'redux-actions';
77
import { decodeToken } from 'tc-accounts';
8+
import { toFSA } from 'utils/redux';
9+
10+
/**
11+
* Handles actions.auth.loadProfile action.
12+
* @param {Object} state
13+
* @param {Object} action
14+
*/
15+
function onProfileLoaded(state, action) {
16+
return {
17+
...state,
18+
profile: action.payload,
19+
};
20+
}
821

922
/**
1023
* Creates a new Auth reducer with the specified initial state.
@@ -13,10 +26,7 @@ import { decodeToken } from 'tc-accounts';
1326
*/
1427
function create(initialState) {
1528
return handleActions({
16-
[actions.auth.loadProfile]: (state, action) => ({
17-
...state,
18-
profile: action.payload,
19-
}),
29+
[actions.auth.loadProfile]: onProfileLoaded,
2030
[actions.auth.setTcTokenV2]: (state, action) => ({
2131
...state,
2232
tokenV2: action.payload,
@@ -44,6 +54,11 @@ export function factory(req) {
4454
tokenV3,
4555
user: tokenV3 ? decodeToken(tokenV3) : null,
4656
};
57+
if (tokenV3) {
58+
return toFSA(actions.auth.loadProfile(tokenV3)).then(res =>
59+
create(onProfileLoaded(state, res)),
60+
);
61+
}
4762
return Promise.resolve(create(state));
4863
}
4964

0 commit comments

Comments
 (0)