Skip to content

Commit e46db9b

Browse files
committed
resolve updateSetting tests with current logic
1 parent fd98175 commit e46db9b

File tree

2 files changed

+201
-58
lines changed

2 files changed

+201
-58
lines changed

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

Lines changed: 201 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,121 +3,265 @@ import { Response as MockResponse } from 'jest-express/lib/response';
33
import { NextFunction as MockNext } from 'jest-express/lib/next';
44
import { User } from '../../../../models/user';
55
import { updateSettings } from '../../authManagement';
6-
import { saveUser, generateToken, userResponse } from '../../helpers';
6+
import { saveUser, generateToken } from '../../helpers';
77
import { createMockUser } from '../../__testUtils__';
88

99
import { mailerService } from '../../../../utils/mail';
10-
import { UserDocument } from '../../../../types';
10+
import { UpdateSettingsRequestBody, UserDocument } from '../../../../types';
1111

1212
jest.mock('../../../../models/user');
1313
jest.mock('../../../../utils/mail');
14+
jest.mock('../../../../views/mail');
1415
jest.mock('../../helpers', () => ({
15-
...jest.requireActual('../../helpers'),
16+
...jest.requireActual('../../helpers'), // use actual userResponse
1617
saveUser: jest.fn(),
1718
generateToken: jest.fn()
1819
}));
1920

20-
describe('user.controller > auth management', () => {
21+
describe('user.controller > auth management > updateSettings (email, username, password)', () => {
2122
let request: any;
2223
let response: any;
2324
let next: MockNext;
25+
let requestBody: UpdateSettingsRequestBody;
26+
let startingUser: Partial<UserDocument>;
27+
2428
const fixedTime = 100000000;
29+
const GENERATED_TOKEN = 'new-token-1io23jijo';
30+
31+
const OLD_USERNAME = 'oldusername';
32+
const NEW_USERNAME = 'newusername';
33+
34+
const OLD_EMAIL = '[email protected]';
35+
const NEW_EMAIL = '[email protected]';
36+
37+
const OLD_PASSWORD = 'oldpassword';
38+
const NEW_PASSWORD = 'newpassword';
39+
40+
// minimum valid request body to manipulate per test
41+
// from manual testing on the account form:
42+
// both username and email are required & there is client-side validation for valid email & username-taken prior to submit
43+
const minimumValidRequest: UpdateSettingsRequestBody = {
44+
username: OLD_USERNAME,
45+
email: OLD_EMAIL
46+
};
47+
48+
beforeAll(() => {
49+
jest.useFakeTimers().setSystemTime(fixedTime);
50+
});
51+
52+
afterAll(() => {
53+
jest.useRealTimers();
54+
});
2555

2656
beforeEach(() => {
2757
request = new MockRequest();
2858
response = new MockResponse();
2959
next = jest.fn();
60+
61+
startingUser = createMockUser({
62+
username: OLD_USERNAME,
63+
email: OLD_EMAIL,
64+
password: OLD_PASSWORD,
65+
id: '123459',
66+
comparePassword: jest.fn().mockResolvedValue(true)
67+
});
68+
69+
User.findById = jest.fn().mockResolvedValue(startingUser);
70+
User.EmailConfirmation = jest.fn().mockReturnValue({ Sent: 'sent' });
71+
(saveUser as jest.Mock).mockResolvedValue(null);
72+
(generateToken as jest.Mock).mockResolvedValue(GENERATED_TOKEN);
73+
(mailerService.send as jest.Mock).mockResolvedValue(true);
74+
75+
request.user = { id: 'valid-id' };
76+
request.headers.host = 'localhost:3000';
3077
});
3178

3279
afterEach(() => {
3380
request.resetMocked();
3481
response.resetMocked();
3582
jest.clearAllMocks();
83+
jest.restoreAllMocks();
3684
});
3785

38-
describe('updateSettings', () => {
39-
beforeAll(() => {
40-
jest.useFakeTimers().setSystemTime(fixedTime);
86+
describe('if the user is not found', () => {
87+
beforeEach(async () => {
88+
(User.findById as jest.Mock).mockResolvedValue(null);
89+
request.user = { id: 'nonexistent-id' };
90+
91+
await updateSettings(request, response, next);
4192
});
4293

43-
afterAll(() => {
44-
jest.useRealTimers();
94+
it('returns 404 and a user-not-found error', async () => {
95+
expect(response.status).toHaveBeenCalledWith(404);
96+
expect(response.json).toHaveBeenCalledWith({
97+
error: 'User not found'
98+
});
4599
});
46100

47-
describe('if the user is not found', () => {
48-
beforeEach(async () => {
49-
User.findById = jest.fn().mockResolvedValue(null);
101+
it('does not save the user', () => {
102+
expect(saveUser).not.toHaveBeenCalled();
103+
});
104+
});
50105

51-
request.user = { id: 'nonexistent-id' };
106+
// the below tests match the current logic, but logic can be improved
107+
describe('if the user is found', () => {
108+
// Q: should we add check & logic that if no username or email are on the request,
109+
// we fallback to the username and/or email on the found user for safety?
110+
// not sure if anyone is hitting this api directly, so the client-side checks may not be enough
52111

53-
(saveUser as jest.Mock).mockResolvedValue(null);
54-
(generateToken as jest.Mock).mockResolvedValue('token12343');
112+
// duplicate username check happens client-side before this request is made
113+
it('saves the user with any username in the request', async () => {
114+
// saves with old username
115+
requestBody = { ...minimumValidRequest, username: OLD_USERNAME };
116+
request.setBody(requestBody);
117+
await updateSettings(request, response, next);
118+
expect(saveUser).toHaveBeenCalledWith(response, { ...startingUser });
55119

56-
await updateSettings(request, response, next);
120+
// saves with new username
121+
requestBody = { ...minimumValidRequest, username: NEW_USERNAME };
122+
request.setBody(requestBody);
123+
await updateSettings(request, response, next);
124+
expect(saveUser).toHaveBeenCalledWith(response, {
125+
...startingUser,
126+
username: NEW_USERNAME
57127
});
128+
});
58129

59-
it('returns 404 and a user-not-found error', async () => {
60-
expect(response.status).toHaveBeenCalledWith(404);
61-
expect(response.json).toHaveBeenCalledWith({
62-
error: 'User not found'
130+
// currently frontend doesn't seem to call password-change related things the below
131+
// not sure if we should update the logic to be cleaner?
132+
describe('when there is a new password in the request', () => {
133+
describe('and the current password is not provided', () => {
134+
beforeEach(async () => {
135+
requestBody = { ...minimumValidRequest, newPassword: NEW_PASSWORD };
136+
request.setBody(requestBody);
137+
await updateSettings(request, response, next);
138+
});
139+
140+
it('returns 401 with a "current password not provided" message', () => {
141+
expect(response.status).toHaveBeenCalledWith(401);
142+
expect(response.json).toHaveBeenCalledWith({
143+
error: 'Current password is not provided.'
144+
});
145+
});
146+
147+
it('does not save the user with the new password', () => {
148+
expect(saveUser).not.toHaveBeenCalled();
63149
});
64-
});
65-
it('does not save the user', () => {
66-
expect(saveUser).not.toHaveBeenCalled();
67150
});
68151
});
69152

70-
// the below tests match the current logic, but logic can be improved
71-
describe('if the user is found', () => {
72-
const startingUser = createMockUser({
73-
username: 'oldusername',
74-
75-
id: 'valid-id',
76-
comparePassword: jest.fn().mockResolvedValue(true)
77-
});
153+
// this should be nested in the previous block but currently here to match existing logic as-is
154+
// NOTE: will make a PR into this branch to propose the change
155+
describe('and when there is a currentPassword in the request', () => {
156+
describe('and the current password does not match', () => {
157+
beforeEach(async () => {
158+
startingUser.comparePassword = jest.fn().mockResolvedValue(false);
78159

79-
beforeEach(() => {
80-
User.findById = jest.fn().mockResolvedValue(startingUser);
160+
requestBody = {
161+
...minimumValidRequest,
162+
newPassword: NEW_PASSWORD,
163+
currentPassword: 'WRONG_PASSWORD'
164+
};
81165

82-
request.user = { id: 'valid-id' };
166+
request.setBody(requestBody);
167+
await updateSettings(request, response, next);
168+
});
83169

84-
(saveUser as jest.Mock).mockResolvedValue(null);
85-
(generateToken as jest.Mock).mockResolvedValue('token12343');
170+
it('returns 401 with a "current password invalid" message', () => {
171+
expect(response.status).toHaveBeenCalledWith(401);
172+
expect(response.json).toHaveBeenCalledWith({
173+
error: 'Current password is invalid.'
174+
});
175+
});
176+
it('does not save the user with the new password', () => {
177+
expect(saveUser).not.toHaveBeenCalled();
178+
});
86179
});
87180

88-
describe('and when there is a username in the request', () => {
181+
describe('and when the current password does match', () => {
89182
beforeEach(async () => {
90-
request.setBody({
91-
username: 'newusername'
92-
});
183+
startingUser.comparePassword = jest.fn().mockResolvedValue(true);
184+
185+
requestBody = {
186+
...minimumValidRequest,
187+
newPassword: NEW_PASSWORD,
188+
currentPassword: OLD_PASSWORD
189+
};
190+
request.setBody(requestBody);
191+
93192
await updateSettings(request, response, next);
94193
});
95-
it('calls saveUser', () => {
194+
it('calls saveUser with the new password', () => {
96195
expect(saveUser).toHaveBeenCalledWith(response, {
97196
...startingUser,
98-
username: 'newusername'
197+
password: NEW_PASSWORD
99198
});
100199
});
101200
});
102201

103-
// currently frontend doesn't seem to call the below
104-
describe('and when there is a newPassword in the request', () => {
105-
beforeEach(async () => {});
106-
describe('and the current password is not provided', () => {
107-
it('returns 401 with a "current password not provided" message', () => {});
108-
it('does not save the user with the new password', () => {});
202+
// NOTE: This should not pass, but it currently does!!
203+
describe('and when there is no new password on the request', () => {
204+
beforeEach(async () => {
205+
startingUser.comparePassword = jest.fn().mockResolvedValue(true);
206+
207+
requestBody = {
208+
...minimumValidRequest,
209+
newPassword: undefined,
210+
currentPassword: OLD_PASSWORD
211+
};
212+
request.setBody(requestBody);
213+
214+
await updateSettings(request, response, next);
109215
});
110-
});
111-
describe('and when there is a currentPassword in the request', () => {
112-
describe('and the current password does not match', () => {
113-
beforeEach(async () => {});
114-
it('returns 401 with a "current password invalid" message', () => {});
115-
it('does not save the user with the new password', () => {});
216+
it('calls saveUser with the new empty password', () => {
217+
expect(saveUser).toHaveBeenCalledWith(response, {
218+
...startingUser,
219+
password: undefined
220+
});
116221
});
117-
describe('and when the current password does match', () => {
118-
beforeEach(async () => {});
119-
it('calls saveUser with the new password', () => {});
222+
});
223+
});
224+
225+
describe('and when there is an email in the request', () => {
226+
it('does not send a verification email if email is unchanged', async () => {
227+
requestBody = minimumValidRequest;
228+
request.setBody(requestBody);
229+
await updateSettings(request, response, next);
230+
231+
expect(saveUser).toHaveBeenCalledWith(response, startingUser);
232+
expect(mailerService.send).not.toHaveBeenCalled();
233+
});
234+
235+
it('updates email and sends verification email if email is changed', async () => {
236+
requestBody = { ...minimumValidRequest, email: NEW_EMAIL };
237+
request.setBody(requestBody);
238+
await updateSettings(request, response, next);
239+
240+
expect(saveUser).toHaveBeenCalledWith(response, {
241+
...startingUser,
242+
email: NEW_EMAIL,
243+
verified: 'sent',
244+
verifiedToken: GENERATED_TOKEN
120245
});
246+
247+
expect(mailerService.send).toHaveBeenCalledWith(
248+
expect.objectContaining({
249+
subject: 'Mock confirm your email'
250+
})
251+
);
252+
});
253+
});
254+
255+
describe('and when there is any other error', () => {
256+
beforeEach(async () => {
257+
User.findById = jest.fn().mockRejectedValue('db error');
258+
requestBody = minimumValidRequest;
259+
request.setBody(requestBody);
260+
await updateSettings(request, response, next);
261+
});
262+
it('returns a 500 error', () => {
263+
expect(response.status).toHaveBeenCalledWith(500);
264+
expect(response.json).toHaveBeenCalledWith({ error: 'db error' });
121265
});
122266
});
123267
});

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ describe('user.controller > signup', () => {
248248
User.findById = jest
249249
.fn()
250250
.mockReturnValue({ exec: jest.fn().mockResolvedValue(mockUser) });
251-
mailerService.send = jest.fn().mockResolvedValue(true);
252251

253252
request.user = { id: 'user1' };
254253
request.headers.host = 'localhost:3000';

0 commit comments

Comments
 (0)