Skip to content

Commit ea21fc1

Browse files
committed
add test util to create mockUser for typesafety
1 parent 3bce71f commit ea21fc1

5 files changed

Lines changed: 143 additions & 154 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Types } from 'mongoose';
2+
import { PublicUser, User, UserDocument, UserPreferences } from '../../types';
3+
import {
4+
CookieConsentOptions,
5+
ApiKeyDocument,
6+
AppThemeOptions
7+
} from '../../types';
8+
9+
/** Mock user preferences for testing. Matches mongoose defaults in User model */
10+
export const mockUserPreferences: UserPreferences = {
11+
fontSize: 18,
12+
lineNumbers: true,
13+
indentationAmount: 2,
14+
isTabIndent: false,
15+
autosave: true,
16+
linewrap: true,
17+
lintWarning: false,
18+
textOutput: false,
19+
gridOutput: false,
20+
theme: AppThemeOptions.LIGHT,
21+
autorefresh: false,
22+
language: 'en-GB',
23+
autocloseBracketsQuotes: true,
24+
autocompleteHinter: false
25+
};
26+
27+
/** Mock sanitised user for testing */
28+
export const mockBaseUserSanitised: PublicUser = {
29+
email: 'test@example.com',
30+
username: 'tester',
31+
preferences: mockUserPreferences,
32+
apiKeys: ([] as unknown) as Types.DocumentArray<ApiKeyDocument>,
33+
verified: 'verified',
34+
id: 'abc123',
35+
totalSize: 42,
36+
cookieConsent: CookieConsentOptions.NONE,
37+
google: 'user@gmail.com',
38+
github: 'user123'
39+
};
40+
41+
/** Mock full user for testing. createdAt is omitted to simplify jest timers where possible */
42+
export const mockBaseUserFull: Omit<User, 'createdAt'> = {
43+
...mockBaseUserSanitised,
44+
name: 'test user',
45+
tokens: [],
46+
password: 'abweorij',
47+
resetPasswordToken: '1i14ij23',
48+
banned: false
49+
};
50+
51+
/**
52+
* Helper function to make mock user document / object for tests
53+
* - Does not attach any document methods
54+
* @param unSanitised - use the entire user type, including sensitive fields
55+
* @param overrides - any overrides on the default mocks --> for clearest tests, always define the properties expected to change
56+
* @returns
57+
*/
58+
export function createMockUser(
59+
overrides: Partial<UserDocument> = {},
60+
unSanitised: boolean = false
61+
): PublicUser & Record<string, any> {
62+
return {
63+
...(unSanitised ? mockBaseUserFull : mockBaseUserSanitised),
64+
...overrides
65+
};
66+
}

server/controllers/user.controller/__tests__/apiKey.test.ts

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { Types } from 'mongoose';
77

88
import { User } from '../../../models/user';
99
import { createApiKey, removeApiKey } from '../apiKey';
10-
import type { ApiKeyDocument, RemoveApiKeyRequestParams } from '../../../types';
10+
import type { ApiKeyDocument } from '../../../types';
11+
import { createMockUser } from '../__testUtils__';
1112

1213
jest.mock('../../../models/user');
1314

1415
describe('user.controller > api key', () => {
15-
let request: MockRequest & { user?: { id: string } };
16-
let response: MockResponse;
16+
let request: any;
17+
let response: any;
1718
let next: MockNext;
1819

1920
beforeEach(() => {
@@ -30,15 +31,11 @@ describe('user.controller > api key', () => {
3031

3132
describe('createApiKey', () => {
3233
it("returns an error if user doesn't exist", async () => {
33-
request.user = { id: '1234' };
34+
request.user = createMockUser({ id: '1234' });
3435

3536
User.findById = jest.fn().mockResolvedValue(null);
3637

37-
await createApiKey(
38-
(request as unknown) as Request,
39-
(response as unknown) as Response,
40-
(next as unknown) as NextFunction
41-
);
38+
await createApiKey(request, response, next);
4239

4340
expect(response.status).toHaveBeenCalledWith(404);
4441
expect(response.json).toHaveBeenCalledWith({
@@ -47,17 +44,13 @@ describe('user.controller > api key', () => {
4744
});
4845

4946
it('returns an error if label not provided', async () => {
50-
request.user = { id: '1234' };
47+
request.user = createMockUser({ id: '1234' });
5148
request.body = {};
5249

5350
const user = new User();
5451
User.findById = jest.fn().mockResolvedValue(user);
5552

56-
await createApiKey(
57-
(request as unknown) as Request,
58-
(response as unknown) as Response,
59-
(next as unknown) as NextFunction
60-
);
53+
await createApiKey(request, response, next);
6154

6255
expect(response.status).toHaveBeenCalledWith(400);
6356
expect(response.json).toHaveBeenCalledWith({
@@ -66,20 +59,16 @@ describe('user.controller > api key', () => {
6659
});
6760

6861
it('returns generated API key to the user', async () => {
62+
request.user = createMockUser({ id: '1234' });
6963
request.setBody({ label: 'my key' });
70-
request.user = { id: '1234' };
7164

7265
const user = new User();
7366
user.apiKeys = ([] as unknown) as Types.DocumentArray<ApiKeyDocument>;
7467

7568
User.findById = jest.fn().mockResolvedValue(user);
7669
user.save = jest.fn();
7770

78-
await createApiKey(
79-
(request as unknown) as Request,
80-
(response as unknown) as Response,
81-
(next as unknown) as NextFunction
82-
);
71+
await createApiKey(request, response, next);
8372

8473
const lastKey = last(user.apiKeys);
8574

@@ -97,15 +86,11 @@ describe('user.controller > api key', () => {
9786

9887
describe('removeApiKey', () => {
9988
it("returns an error if user doesn't exist", async () => {
100-
request.user = { id: '1234' };
89+
request.user = createMockUser({ id: '1234' });
10190

10291
User.findById = jest.fn().mockResolvedValue(null);
10392

104-
await removeApiKey(
105-
(request as unknown) as Request<RemoveApiKeyRequestParams>,
106-
(response as unknown) as Response,
107-
(next as unknown) as NextFunction
108-
);
93+
await removeApiKey(request, response, next);
10994

11095
expect(response.status).toHaveBeenCalledWith(404);
11196
expect(response.json).toHaveBeenCalledWith({
@@ -114,18 +99,14 @@ describe('user.controller > api key', () => {
11499
});
115100

116101
it("returns an error if specified key doesn't exist", async () => {
117-
request.user = { id: '1234' };
102+
request.user = createMockUser({ id: '1234' });
118103
request.params = { keyId: 'not-a-real-key' };
119104
const user = new User();
120105
user.apiKeys = ([] as unknown) as Types.DocumentArray<ApiKeyDocument>;
121106

122107
User.findById = jest.fn().mockResolvedValue(user);
123108

124-
await removeApiKey(
125-
(request as unknown) as Request<RemoveApiKeyRequestParams>,
126-
(response as unknown) as Response,
127-
(next as unknown) as NextFunction
128-
);
109+
await removeApiKey(request, response, next);
129110

130111
expect(response.status).toHaveBeenCalledWith(404);
131112
expect(response.json).toHaveBeenCalledWith({
@@ -144,21 +125,18 @@ describe('user.controller > api key', () => {
144125
apiKeys.find = Array.prototype.find;
145126
apiKeys.pull = jest.fn();
146127

147-
const user = {
128+
const user = createMockUser({
129+
id: '1234',
148130
apiKeys,
149131
save: jest.fn()
150-
};
132+
});
151133

152-
request.user = { id: '1234' };
134+
request.user = user;
153135
request.params = { keyId: 'id1' };
154136

155137
User.findById = jest.fn().mockResolvedValue(user);
156138

157-
await removeApiKey(
158-
(request as unknown) as Request<RemoveApiKeyRequestParams>,
159-
(response as unknown) as Response,
160-
(next as unknown) as NextFunction
161-
);
139+
await removeApiKey(request, response, next);
162140

163141
expect(user.apiKeys.pull).toHaveBeenCalledWith({ _id: 'id1' });
164142
expect(user.save).toHaveBeenCalled();

server/controllers/user.controller/__tests__/authManagement.test.ts

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
unlinkGoogle
1212
} from '../authManagement';
1313
import { saveUser, generateToken, userResponse } from '../helpers';
14+
import { createMockUser } from '../__testUtils__';
1415

1516
import { mailerService } from '../../../utils/mail';
1617
import { UserDocument } from '../../../types';
@@ -68,11 +69,11 @@ describe('user.controller > auth management', () => {
6869
describe('if the user is found', () => {
6970
beforeEach(() => {
7071
mockToken = 'mock-token';
71-
saveMock = jest.fn().mockResolvedValue({});
72-
mockUser = {
72+
saveMock = jest.fn().mockResolvedValue(null);
73+
mockUser = createMockUser({
7374
email: 'test@example.com',
7475
save: saveMock
75-
};
76+
});
7677

7778
(generateToken as jest.Mock).mockResolvedValue(mockToken);
7879
User.findByEmail = jest.fn().mockResolvedValue(mockUser);
@@ -113,10 +114,10 @@ describe('user.controller > auth management', () => {
113114
beforeEach(() => {
114115
mockToken = 'mock-token';
115116
saveMock = jest.fn().mockResolvedValue({});
116-
mockUser = {
117+
mockUser = createMockUser({
117118
email: 'test@example.com',
118119
save: saveMock
119-
};
120+
});
120121

121122
(generateToken as jest.Mock).mockResolvedValue(mockToken);
122123
User.findByEmail = jest.fn().mockResolvedValue(null);
@@ -142,10 +143,10 @@ describe('user.controller > auth management', () => {
142143
it('returns unsuccessful for all other errors', async () => {
143144
mockToken = 'mock-token';
144145
saveMock = jest.fn().mockResolvedValue({});
145-
mockUser = {
146+
mockUser = createMockUser({
146147
email: 'test@example.com',
147148
save: saveMock
148-
};
149+
});
149150

150151
(generateToken as jest.Mock).mockRejectedValue(
151152
new Error('network error')
@@ -202,11 +203,12 @@ describe('user.controller > auth management', () => {
202203

203204
describe('and when there is a user with valid token', () => {
204205
beforeEach(async () => {
205-
const fakeUser = {
206+
const fakeUser = createMockUser({
206207
email: 'test@example.com',
207208
resetPasswordToken: 'valid-token',
208209
resetPasswordExpires: fixedTime + 10000 // still valid
209-
};
210+
});
211+
210212
User.findOne = jest.fn().mockReturnValue({
211213
exec: jest.fn().mockResolvedValue(fakeUser)
212214
});
@@ -257,13 +259,14 @@ describe('user.controller > auth management', () => {
257259
});
258260

259261
describe('and when there is a user with valid token', () => {
260-
const fakeUser = {
262+
const fakeUser = createMockUser({
261263
email: 'test@example.com',
262264
password: 'oldpassword',
263265
resetPasswordToken: 'valid-token',
264266
resetPasswordExpires: fixedTime + 10000, // still valid
265267
save: jest.fn()
266-
};
268+
});
269+
267270
beforeEach(async () => {
268271
User.findOne = jest.fn().mockReturnValue({
269272
exec: jest.fn().mockResolvedValue(fakeUser)
@@ -320,11 +323,11 @@ describe('user.controller > auth management', () => {
320323

321324
// the below tests match the current logic, but logic can be improved
322325
describe('if the user is found', () => {
323-
const startingUser = {
326+
const startingUser = createMockUser({
324327
username: 'oldusername',
325328
email: 'old@email.com',
326329
id: 'valid-id'
327-
};
330+
});
328331

329332
beforeEach(() => {
330333
User.findById = jest.fn().mockResolvedValue(startingUser);
@@ -399,13 +402,10 @@ describe('user.controller > auth management', () => {
399402
});
400403
});
401404
describe('and when there is a user in the request', () => {
402-
const user = {
403-
github: { id: '123', username: 'testuser' },
404-
tokens: [
405-
{ kind: 'github', accessToken: 'abc' },
406-
{ kind: 'google', accessToken: 'xyz' }
407-
]
408-
};
405+
const user = createMockUser({
406+
github: 'testuser',
407+
tokens: [{ kind: 'github' }, { kind: 'google' }]
408+
});
409409

410410
beforeEach(async () => {
411411
request.user = user;
@@ -415,7 +415,7 @@ describe('user.controller > auth management', () => {
415415
expect(user.github).toBeUndefined();
416416
});
417417
it('filters out the github token', () => {
418-
expect(user.tokens).toEqual([{ kind: 'google', accessToken: 'xyz' }]);
418+
expect(user.tokens).toEqual([{ kind: 'google' }]);
419419
});
420420
it('does calls saveUser', () => {
421421
expect(saveUser).toHaveBeenCalledWith(response, user);
@@ -440,13 +440,10 @@ describe('user.controller > auth management', () => {
440440
});
441441
});
442442
describe('and when there is a user in the request', () => {
443-
const user = {
444-
google: { id: '123', username: 'testuser' },
445-
tokens: [
446-
{ kind: 'github', accessToken: 'abc' },
447-
{ kind: 'google', accessToken: 'xyz' }
448-
]
449-
};
443+
const user = createMockUser({
444+
google: 'testuser',
445+
tokens: [{ kind: 'github' }, { kind: 'google' }]
446+
});
450447

451448
beforeEach(async () => {
452449
request.user = user;
@@ -456,7 +453,7 @@ describe('user.controller > auth management', () => {
456453
expect(user.google).toBeUndefined();
457454
});
458455
it('filters out the google token', () => {
459-
expect(user.tokens).toEqual([{ kind: 'github', accessToken: 'abc' }]);
456+
expect(user.tokens).toEqual([{ kind: 'github' }]);
460457
});
461458
it('does calls saveUser', () => {
462459
expect(saveUser).toHaveBeenCalledWith(response, user);

0 commit comments

Comments
 (0)