nicot-simple-user 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +216 -0
- package/dist/simple-user/login/login.controller.d.ts +3 -3
- package/dist/simple-user/login/login.controller.js +8 -8
- package/dist/simple-user/login/login.controller.js.map +1 -1
- package/dist/simple-user/options.d.ts +3 -2
- package/dist/simple-user/send-code/decorators.d.ts +1 -0
- package/dist/simple-user/send-code/decorators.js +4 -2
- package/dist/simple-user/send-code/decorators.js.map +1 -1
- package/dist/simple-user/send-code/send-code.controller.js +1 -0
- package/dist/simple-user/send-code/send-code.controller.js.map +1 -1
- package/dist/simple-user/send-code/send-code.dto.d.ts +10 -1
- package/dist/simple-user/send-code/send-code.dto.js +38 -3
- package/dist/simple-user/send-code/send-code.dto.js.map +1 -1
- package/dist/simple-user/send-code/send-code.service.js +31 -11
- package/dist/simple-user/send-code/send-code.service.js.map +1 -1
- package/dist/simple-user/simple-user/change-mobile.dto.d.ts +3 -0
- package/dist/simple-user/simple-user/change-mobile.dto.js +12 -0
- package/dist/simple-user/simple-user/change-mobile.dto.js.map +1 -0
- package/dist/simple-user/simple-user/contact.dto.d.ts +16 -0
- package/dist/simple-user/simple-user/contact.dto.js +88 -0
- package/dist/simple-user/simple-user/contact.dto.js.map +1 -0
- package/dist/simple-user/simple-user/login.dto.d.ts +2 -2
- package/dist/simple-user/simple-user/login.dto.js +3 -3
- package/dist/simple-user/simple-user/login.dto.js.map +1 -1
- package/dist/simple-user/simple-user/mobile.dto.d.ts +6 -0
- package/dist/simple-user/simple-user/mobile.dto.js +47 -0
- package/dist/simple-user/simple-user/mobile.dto.js.map +1 -0
- package/dist/simple-user/simple-user/reset-password.dto.d.ts +2 -2
- package/dist/simple-user/simple-user/reset-password.dto.js +2 -2
- package/dist/simple-user/simple-user/reset-password.dto.js.map +1 -1
- package/dist/simple-user/simple-user/simple-user.service.d.ts +9 -4
- package/dist/simple-user/simple-user/simple-user.service.js +102 -60
- package/dist/simple-user/simple-user/simple-user.service.js.map +1 -1
- package/dist/simple-user/simple-user.entity.d.ts +3 -1
- package/dist/simple-user/simple-user.entity.js +20 -4
- package/dist/simple-user/simple-user.entity.js.map +1 -1
- package/dist/simple-user/user-center/user-center.controller.d.ts +2 -0
- package/dist/simple-user/user-center/user-center.controller.js +18 -0
- package/dist/simple-user/user-center/user-center.controller.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/test/app-e2e-v2.spec.ts +621 -0
- package/test/app-e2e.spec.ts +3 -0
- package/test/docker-compose.yml +14 -0
package/package.json
CHANGED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { INestApplication } from '@nestjs/common';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { AppModule } from './../src/app.module';
|
|
5
|
+
import { AppUser } from '../src/app-user.entity';
|
|
6
|
+
import { GenericReturnMessageDto } from 'nicot';
|
|
7
|
+
import { LoginResponseDto } from '../src/simple-user/simple-user/login.dto';
|
|
8
|
+
|
|
9
|
+
describe('SimpleUserModule v2 - mobile support (e2e)', () => {
|
|
10
|
+
let app: INestApplication;
|
|
11
|
+
let httpServer: any;
|
|
12
|
+
|
|
13
|
+
const EMAIL_CODE = '123456';
|
|
14
|
+
const SMS_CODE = '654321';
|
|
15
|
+
|
|
16
|
+
const rand = Math.random().toString(16).slice(2);
|
|
17
|
+
const email1 = `v2_${rand}@example.com`;
|
|
18
|
+
const email2 = `v2b_${rand}@example.com`;
|
|
19
|
+
const mobile1 = `86 1380000${rand.slice(0, 4)}`;
|
|
20
|
+
const mobile2 = `86 1390000${rand.slice(0, 4)}`;
|
|
21
|
+
|
|
22
|
+
const ssaid = `ssaid_v2_${rand}`;
|
|
23
|
+
const ssaid2 = `ssaid_v2b_${rand}`;
|
|
24
|
+
|
|
25
|
+
const password1 = `P@ss_${rand}_1`;
|
|
26
|
+
const password2 = `P@ss_${rand}_2`;
|
|
27
|
+
|
|
28
|
+
let token: string | undefined;
|
|
29
|
+
let userId: number | undefined;
|
|
30
|
+
|
|
31
|
+
let mobileToken: string | undefined;
|
|
32
|
+
let mobileUserId: number | undefined;
|
|
33
|
+
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
36
|
+
imports: [AppModule],
|
|
37
|
+
}).compile();
|
|
38
|
+
|
|
39
|
+
app = moduleFixture.createNestApplication();
|
|
40
|
+
await app.init();
|
|
41
|
+
httpServer = app.getHttpServer();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(async () => {
|
|
45
|
+
await app.close();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function expectOkEnvelope(resBody: any) {
|
|
49
|
+
expect(resBody).toHaveProperty('statusCode');
|
|
50
|
+
expect(resBody).toHaveProperty('message');
|
|
51
|
+
expect(resBody).toHaveProperty('success');
|
|
52
|
+
expect(resBody).toHaveProperty('timestamp');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ──────────────────────────────────────────────────────────
|
|
56
|
+
// 1. Validation: must provide exactly one of email or mobile
|
|
57
|
+
// ──────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
describe('ContactDto validation', () => {
|
|
60
|
+
it('should reject when neither email nor mobile is provided', async () => {
|
|
61
|
+
const res = await request(httpServer)
|
|
62
|
+
.get('/login/user-exists')
|
|
63
|
+
.query({})
|
|
64
|
+
.expect(400);
|
|
65
|
+
|
|
66
|
+
expect(res.body.statusCode).toBe(400);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should reject when both email and mobile are provided', async () => {
|
|
70
|
+
const res = await request(httpServer)
|
|
71
|
+
.get('/login/user-exists')
|
|
72
|
+
.query({ email: email1, mobile: mobile1 })
|
|
73
|
+
.expect(400);
|
|
74
|
+
|
|
75
|
+
expect(res.body.statusCode).toBe(400);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should accept when only email is provided', async () => {
|
|
79
|
+
const res = await request(httpServer)
|
|
80
|
+
.get('/login/user-exists')
|
|
81
|
+
.query({ email: email1 })
|
|
82
|
+
.expect(200);
|
|
83
|
+
|
|
84
|
+
expectOkEnvelope(res.body);
|
|
85
|
+
expect(res.body.data.exists).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should accept when only mobile is provided', async () => {
|
|
89
|
+
const res = await request(httpServer)
|
|
90
|
+
.get('/login/user-exists')
|
|
91
|
+
.query({ mobile: mobile1 })
|
|
92
|
+
.expect(200);
|
|
93
|
+
|
|
94
|
+
expectOkEnvelope(res.body);
|
|
95
|
+
expect(res.body.data.exists).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ──────────────────────────────────────────────────────────
|
|
100
|
+
// 2. Mobile registration & login flow
|
|
101
|
+
// ──────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
describe('Mobile registration & login', () => {
|
|
104
|
+
it('POST /send-code/send (mobile, Login) -> should succeed', async () => {
|
|
105
|
+
const res = await request(httpServer)
|
|
106
|
+
.post('/send-code/send')
|
|
107
|
+
.set('x-client-ssaid', ssaid)
|
|
108
|
+
.send({ mobile: mobile1, codePurpose: 'Login' })
|
|
109
|
+
.expect(200);
|
|
110
|
+
|
|
111
|
+
expectOkEnvelope(res.body);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('GET /send-code/verify (mobile, Login) -> correct SMS code should pass; wrong code should 403', async () => {
|
|
115
|
+
const ok = await request(httpServer)
|
|
116
|
+
.get('/send-code/verify')
|
|
117
|
+
.query({ mobile: mobile1, codePurpose: 'Login', code: SMS_CODE })
|
|
118
|
+
.expect(200);
|
|
119
|
+
|
|
120
|
+
expectOkEnvelope(ok.body);
|
|
121
|
+
|
|
122
|
+
const bad = await request(httpServer)
|
|
123
|
+
.get('/send-code/verify')
|
|
124
|
+
.query({ mobile: mobile1, codePurpose: 'Login', code: '000000' })
|
|
125
|
+
.expect(403);
|
|
126
|
+
|
|
127
|
+
expectOkEnvelope(bad.body);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('POST /login (mobile + code) -> should auto-create user & return token', async () => {
|
|
131
|
+
const res = await request(httpServer)
|
|
132
|
+
.post('/login')
|
|
133
|
+
.set('x-client-ssaid', ssaid)
|
|
134
|
+
.send({ mobile: mobile1, code: SMS_CODE })
|
|
135
|
+
.expect(200);
|
|
136
|
+
|
|
137
|
+
expectOkEnvelope(res.body);
|
|
138
|
+
const data = (res.body as GenericReturnMessageDto<LoginResponseDto>).data;
|
|
139
|
+
expect(data).toBeDefined();
|
|
140
|
+
expect(typeof data.userId).toBe('number');
|
|
141
|
+
expect(data.token).toHaveLength(64);
|
|
142
|
+
|
|
143
|
+
mobileToken = data.token;
|
|
144
|
+
mobileUserId = data.userId;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('GET /login/user-exists (mobile) -> should be true after registration', async () => {
|
|
148
|
+
const res = await request(httpServer)
|
|
149
|
+
.get('/login/user-exists')
|
|
150
|
+
.query({ mobile: mobile1 })
|
|
151
|
+
.expect(200);
|
|
152
|
+
|
|
153
|
+
expectOkEnvelope(res.body);
|
|
154
|
+
expect(res.body.data.exists).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('GET /user-center/me -> should show mobile, registered=true, no email', async () => {
|
|
158
|
+
const res = await request(httpServer)
|
|
159
|
+
.get('/user-center/me')
|
|
160
|
+
.set('x-client-ssaid', ssaid)
|
|
161
|
+
.set('x-client-token', mobileToken)
|
|
162
|
+
.expect(200);
|
|
163
|
+
|
|
164
|
+
expectOkEnvelope(res.body);
|
|
165
|
+
const data = (res.body as GenericReturnMessageDto<AppUser>).data;
|
|
166
|
+
expect(data.id).toBe(mobileUserId);
|
|
167
|
+
expect(data.mobile).toBe(mobile1);
|
|
168
|
+
expect(data.registered).toBe(true);
|
|
169
|
+
expect(data.passwordSet).toBe(false);
|
|
170
|
+
expect(data.email).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ──────────────────────────────────────────────────────────
|
|
175
|
+
// 3. Mobile user: set password & login by password
|
|
176
|
+
// ──────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
describe('Mobile user: password flow', () => {
|
|
179
|
+
it('POST /user-center/change-password -> set password for mobile user', async () => {
|
|
180
|
+
const res = await request(httpServer)
|
|
181
|
+
.post('/user-center/change-password')
|
|
182
|
+
.set('x-client-ssaid', ssaid)
|
|
183
|
+
.set('x-client-token', mobileToken)
|
|
184
|
+
.send({ newPassword: password1 })
|
|
185
|
+
.expect(200);
|
|
186
|
+
|
|
187
|
+
expectOkEnvelope(res.body);
|
|
188
|
+
|
|
189
|
+
// changePassword kicks sessions, re-login with mobile + password
|
|
190
|
+
const loginOk = await request(httpServer)
|
|
191
|
+
.post('/login')
|
|
192
|
+
.set('x-client-ssaid', ssaid)
|
|
193
|
+
.send({ mobile: mobile1, password: password1 })
|
|
194
|
+
.expect(200);
|
|
195
|
+
|
|
196
|
+
expectOkEnvelope(loginOk.body);
|
|
197
|
+
const data = (loginOk.body as GenericReturnMessageDto<LoginResponseDto>)
|
|
198
|
+
.data;
|
|
199
|
+
expect(data.token).toHaveLength(64);
|
|
200
|
+
mobileToken = data.token;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('POST /login (mobile + wrong password) -> should 403', async () => {
|
|
204
|
+
const res = await request(httpServer)
|
|
205
|
+
.post('/login')
|
|
206
|
+
.set('x-client-ssaid', ssaid)
|
|
207
|
+
.send({ mobile: mobile1, password: 'WRONG' })
|
|
208
|
+
.expect(403);
|
|
209
|
+
|
|
210
|
+
expectOkEnvelope(res.body);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('POST /login (mobile + only password, user not exist) -> should 404', async () => {
|
|
214
|
+
const res = await request(httpServer)
|
|
215
|
+
.post('/login')
|
|
216
|
+
.set('x-client-ssaid', ssaid)
|
|
217
|
+
.send({ mobile: '86 19999999999', password: password1 })
|
|
218
|
+
.expect(404);
|
|
219
|
+
|
|
220
|
+
expectOkEnvelope(res.body);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ──────────────────────────────────────────────────────────
|
|
225
|
+
// 4. Change mobile
|
|
226
|
+
// ──────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
describe('Change mobile', () => {
|
|
229
|
+
it('POST /send-code/send (ChangeMobile) -> should succeed', async () => {
|
|
230
|
+
const res = await request(httpServer)
|
|
231
|
+
.post('/send-code/send')
|
|
232
|
+
.set('x-client-ssaid', ssaid)
|
|
233
|
+
.send({ mobile: mobile2, codePurpose: 'ChangeMobile' })
|
|
234
|
+
.expect(200);
|
|
235
|
+
|
|
236
|
+
expectOkEnvelope(res.body);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('POST /user-center/change-mobile -> should change mobile', async () => {
|
|
240
|
+
const res = await request(httpServer)
|
|
241
|
+
.post('/user-center/change-mobile')
|
|
242
|
+
.set('x-client-ssaid', ssaid)
|
|
243
|
+
.set('x-client-token', mobileToken)
|
|
244
|
+
.send({ mobile: mobile2, code: SMS_CODE })
|
|
245
|
+
.expect(200);
|
|
246
|
+
|
|
247
|
+
expectOkEnvelope(res.body);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('POST /login (old mobile + password) -> should 404 (no user with old mobile)', async () => {
|
|
251
|
+
const res = await request(httpServer)
|
|
252
|
+
.post('/login')
|
|
253
|
+
.set('x-client-ssaid', ssaid)
|
|
254
|
+
.send({ mobile: mobile1, password: password1 })
|
|
255
|
+
.expect(404);
|
|
256
|
+
|
|
257
|
+
expectOkEnvelope(res.body);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('POST /login (new mobile + password) -> should succeed', async () => {
|
|
261
|
+
const res = await request(httpServer)
|
|
262
|
+
.post('/login')
|
|
263
|
+
.set('x-client-ssaid', ssaid)
|
|
264
|
+
.send({ mobile: mobile2, password: password1 })
|
|
265
|
+
.expect(200);
|
|
266
|
+
|
|
267
|
+
expectOkEnvelope(res.body);
|
|
268
|
+
const data = (res.body as GenericReturnMessageDto<LoginResponseDto>)
|
|
269
|
+
.data;
|
|
270
|
+
expect(data.token).toHaveLength(64);
|
|
271
|
+
mobileToken = data.token;
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ──────────────────────────────────────────────────────────
|
|
276
|
+
// 5. Reset password via mobile
|
|
277
|
+
// ──────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
describe('Reset password via mobile', () => {
|
|
280
|
+
it('POST /send-code/send (mobile, ResetPassword) -> should succeed', async () => {
|
|
281
|
+
const res = await request(httpServer)
|
|
282
|
+
.post('/send-code/send')
|
|
283
|
+
.set('x-client-ssaid', ssaid)
|
|
284
|
+
.send({ mobile: mobile2, codePurpose: 'ResetPassword' })
|
|
285
|
+
.expect(200);
|
|
286
|
+
|
|
287
|
+
expectOkEnvelope(res.body);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('POST /login/reset-password (mobile) -> should reset password', async () => {
|
|
291
|
+
const res = await request(httpServer)
|
|
292
|
+
.post('/login/reset-password')
|
|
293
|
+
.send({ mobile: mobile2, code: SMS_CODE, newPassword: password2 })
|
|
294
|
+
.expect(200);
|
|
295
|
+
|
|
296
|
+
expectOkEnvelope(res.body);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('POST /login (mobile + old password) -> should 403', async () => {
|
|
300
|
+
const res = await request(httpServer)
|
|
301
|
+
.post('/login')
|
|
302
|
+
.set('x-client-ssaid', ssaid)
|
|
303
|
+
.send({ mobile: mobile2, password: password1 })
|
|
304
|
+
.expect(403);
|
|
305
|
+
|
|
306
|
+
expectOkEnvelope(res.body);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('POST /login (mobile + new password) -> should succeed', async () => {
|
|
310
|
+
const res = await request(httpServer)
|
|
311
|
+
.post('/login')
|
|
312
|
+
.set('x-client-ssaid', ssaid)
|
|
313
|
+
.send({ mobile: mobile2, password: password2 })
|
|
314
|
+
.expect(200);
|
|
315
|
+
|
|
316
|
+
expectOkEnvelope(res.body);
|
|
317
|
+
mobileToken = (
|
|
318
|
+
res.body as GenericReturnMessageDto<LoginResponseDto>
|
|
319
|
+
).data.token;
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// ──────────────────────────────────────────────────────────
|
|
324
|
+
// 6. Unregister via mobile
|
|
325
|
+
// ──────────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
describe('Unregister via mobile', () => {
|
|
328
|
+
it('POST /user-center/unregister -> should revoke session', async () => {
|
|
329
|
+
const res = await request(httpServer)
|
|
330
|
+
.post('/user-center/unregister')
|
|
331
|
+
.set('x-client-ssaid', ssaid)
|
|
332
|
+
.set('x-client-token', mobileToken)
|
|
333
|
+
.expect(200);
|
|
334
|
+
|
|
335
|
+
expectOkEnvelope(res.body);
|
|
336
|
+
|
|
337
|
+
const me = await request(httpServer)
|
|
338
|
+
.get('/user-center/me')
|
|
339
|
+
.set('x-client-ssaid', ssaid)
|
|
340
|
+
.set('x-client-token', mobileToken);
|
|
341
|
+
|
|
342
|
+
expect(me.status).toBe(401);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('POST /login/unregister-with-code (mobile) -> should succeed', async () => {
|
|
346
|
+
await request(httpServer)
|
|
347
|
+
.post('/send-code/send')
|
|
348
|
+
.set('x-client-ssaid', ssaid)
|
|
349
|
+
.send({ mobile: mobile2, codePurpose: 'Unregister' })
|
|
350
|
+
.expect(200);
|
|
351
|
+
|
|
352
|
+
const res = await request(httpServer)
|
|
353
|
+
.post('/login/unregister-with-code')
|
|
354
|
+
.send({ mobile: mobile2, code: SMS_CODE })
|
|
355
|
+
.expect(200);
|
|
356
|
+
|
|
357
|
+
expectOkEnvelope(res.body);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// ──────────────────────────────────────────────────────────
|
|
362
|
+
// 7. Email registration with setPassword, then add mobile
|
|
363
|
+
// ──────────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
describe('Email registration + add mobile (cross-contact)', () => {
|
|
366
|
+
it('POST /login (email + code + setPassword) -> register with email', async () => {
|
|
367
|
+
await request(httpServer)
|
|
368
|
+
.post('/send-code/send')
|
|
369
|
+
.set('x-client-ssaid', ssaid2)
|
|
370
|
+
.send({ email: email1, codePurpose: 'Login' })
|
|
371
|
+
.expect(200);
|
|
372
|
+
|
|
373
|
+
const res = await request(httpServer)
|
|
374
|
+
.post('/login')
|
|
375
|
+
.set('x-client-ssaid', ssaid2)
|
|
376
|
+
.send({ email: email1, code: EMAIL_CODE, setPassword: password1 })
|
|
377
|
+
.expect(200);
|
|
378
|
+
|
|
379
|
+
expectOkEnvelope(res.body);
|
|
380
|
+
const data = (res.body as GenericReturnMessageDto<LoginResponseDto>).data;
|
|
381
|
+
token = data.token;
|
|
382
|
+
userId = data.userId;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('GET /user-center/me -> should have email but no mobile', async () => {
|
|
386
|
+
const res = await request(httpServer)
|
|
387
|
+
.get('/user-center/me')
|
|
388
|
+
.set('x-client-ssaid', ssaid2)
|
|
389
|
+
.set('x-client-token', token)
|
|
390
|
+
.expect(200);
|
|
391
|
+
|
|
392
|
+
const data = (res.body as GenericReturnMessageDto<AppUser>).data;
|
|
393
|
+
expect(data.email).toBe(email1);
|
|
394
|
+
expect(data.mobile).toBeNull();
|
|
395
|
+
expect(data.registered).toBe(true);
|
|
396
|
+
expect(data.passwordSet).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('POST /user-center/change-mobile -> add mobile to email user', async () => {
|
|
400
|
+
await request(httpServer)
|
|
401
|
+
.post('/send-code/send')
|
|
402
|
+
.set('x-client-ssaid', ssaid2)
|
|
403
|
+
.send({ mobile: mobile1, codePurpose: 'ChangeMobile' })
|
|
404
|
+
.expect(200);
|
|
405
|
+
|
|
406
|
+
const res = await request(httpServer)
|
|
407
|
+
.post('/user-center/change-mobile')
|
|
408
|
+
.set('x-client-ssaid', ssaid2)
|
|
409
|
+
.set('x-client-token', token)
|
|
410
|
+
.send({ mobile: mobile1, code: SMS_CODE })
|
|
411
|
+
.expect(200);
|
|
412
|
+
|
|
413
|
+
expectOkEnvelope(res.body);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('GET /user-center/me -> should now have both email and mobile', async () => {
|
|
417
|
+
const res = await request(httpServer)
|
|
418
|
+
.get('/user-center/me')
|
|
419
|
+
.set('x-client-ssaid', ssaid2)
|
|
420
|
+
.set('x-client-token', token)
|
|
421
|
+
.expect(200);
|
|
422
|
+
|
|
423
|
+
const data = (res.body as GenericReturnMessageDto<AppUser>).data;
|
|
424
|
+
expect(data.email).toBe(email1);
|
|
425
|
+
expect(data.mobile).toBe(mobile1);
|
|
426
|
+
expect(data.registered).toBe(true);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should be able to login with email + password', async () => {
|
|
430
|
+
const res = await request(httpServer)
|
|
431
|
+
.post('/login')
|
|
432
|
+
.set('x-client-ssaid', ssaid2)
|
|
433
|
+
.send({ email: email1, password: password1 })
|
|
434
|
+
.expect(200);
|
|
435
|
+
|
|
436
|
+
expectOkEnvelope(res.body);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should be able to login with mobile + password', async () => {
|
|
440
|
+
const res = await request(httpServer)
|
|
441
|
+
.post('/login')
|
|
442
|
+
.set('x-client-ssaid', ssaid2)
|
|
443
|
+
.send({ mobile: mobile1, password: password1 })
|
|
444
|
+
.expect(200);
|
|
445
|
+
|
|
446
|
+
expectOkEnvelope(res.body);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('user-exists should return true for both email and mobile', async () => {
|
|
450
|
+
const byEmail = await request(httpServer)
|
|
451
|
+
.get('/login/user-exists')
|
|
452
|
+
.query({ email: email1 })
|
|
453
|
+
.expect(200);
|
|
454
|
+
|
|
455
|
+
expect(byEmail.body.data.exists).toBe(true);
|
|
456
|
+
|
|
457
|
+
const byMobile = await request(httpServer)
|
|
458
|
+
.get('/login/user-exists')
|
|
459
|
+
.query({ mobile: mobile1 })
|
|
460
|
+
.expect(200);
|
|
461
|
+
|
|
462
|
+
expect(byMobile.body.data.exists).toBe(true);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ──────────────────────────────────────────────────────────
|
|
467
|
+
// 8. Change email on a user that has both email + mobile
|
|
468
|
+
// ──────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
describe('Change email on dual-contact user', () => {
|
|
471
|
+
it('POST /user-center/change-email -> change email', async () => {
|
|
472
|
+
await request(httpServer)
|
|
473
|
+
.post('/send-code/send')
|
|
474
|
+
.set('x-client-ssaid', ssaid2)
|
|
475
|
+
.send({ email: email2, codePurpose: 'ChangeEmail' })
|
|
476
|
+
.expect(200);
|
|
477
|
+
|
|
478
|
+
const res = await request(httpServer)
|
|
479
|
+
.post('/user-center/change-email')
|
|
480
|
+
.set('x-client-ssaid', ssaid2)
|
|
481
|
+
.set('x-client-token', token)
|
|
482
|
+
.send({ email: email2, code: EMAIL_CODE })
|
|
483
|
+
.expect(200);
|
|
484
|
+
|
|
485
|
+
expectOkEnvelope(res.body);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('GET /user-center/me -> should show new email, same mobile', async () => {
|
|
489
|
+
const res = await request(httpServer)
|
|
490
|
+
.get('/user-center/me')
|
|
491
|
+
.set('x-client-ssaid', ssaid2)
|
|
492
|
+
.set('x-client-token', token)
|
|
493
|
+
.expect(200);
|
|
494
|
+
|
|
495
|
+
const data = (res.body as GenericReturnMessageDto<AppUser>).data;
|
|
496
|
+
expect(data.email).toBe(email2);
|
|
497
|
+
expect(data.mobile).toBe(mobile1);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('login with old email should 404, new email should succeed', async () => {
|
|
501
|
+
const oldFail = await request(httpServer)
|
|
502
|
+
.post('/login')
|
|
503
|
+
.set('x-client-ssaid', ssaid2)
|
|
504
|
+
.send({ email: email1, password: password1 })
|
|
505
|
+
.expect(404);
|
|
506
|
+
|
|
507
|
+
expectOkEnvelope(oldFail.body);
|
|
508
|
+
|
|
509
|
+
const newOk = await request(httpServer)
|
|
510
|
+
.post('/login')
|
|
511
|
+
.set('x-client-ssaid', ssaid2)
|
|
512
|
+
.send({ email: email2, password: password1 })
|
|
513
|
+
.expect(200);
|
|
514
|
+
|
|
515
|
+
expectOkEnvelope(newOk.body);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('login with mobile should still succeed', async () => {
|
|
519
|
+
const res = await request(httpServer)
|
|
520
|
+
.post('/login')
|
|
521
|
+
.set('x-client-ssaid', ssaid2)
|
|
522
|
+
.send({ mobile: mobile1, password: password1 })
|
|
523
|
+
.expect(200);
|
|
524
|
+
|
|
525
|
+
expectOkEnvelope(res.body);
|
|
526
|
+
token = (
|
|
527
|
+
res.body as GenericReturnMessageDto<LoginResponseDto>
|
|
528
|
+
).data.token;
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// ──────────────────────────────────────────────────────────
|
|
533
|
+
// 9. Reset password via mobile, verify email login also uses new password
|
|
534
|
+
// ──────────────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
describe('Reset password via mobile affects email login too', () => {
|
|
537
|
+
it('reset password via mobile', async () => {
|
|
538
|
+
await request(httpServer)
|
|
539
|
+
.post('/send-code/send')
|
|
540
|
+
.set('x-client-ssaid', ssaid2)
|
|
541
|
+
.send({ mobile: mobile1, codePurpose: 'ResetPassword' })
|
|
542
|
+
.expect(200);
|
|
543
|
+
|
|
544
|
+
await request(httpServer)
|
|
545
|
+
.post('/login/reset-password')
|
|
546
|
+
.send({ mobile: mobile1, code: SMS_CODE, newPassword: password2 })
|
|
547
|
+
.expect(200);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('login with email + old password should 403', async () => {
|
|
551
|
+
const res = await request(httpServer)
|
|
552
|
+
.post('/login')
|
|
553
|
+
.set('x-client-ssaid', ssaid2)
|
|
554
|
+
.send({ email: email2, password: password1 })
|
|
555
|
+
.expect(403);
|
|
556
|
+
|
|
557
|
+
expectOkEnvelope(res.body);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('login with email + new password should succeed', async () => {
|
|
561
|
+
const res = await request(httpServer)
|
|
562
|
+
.post('/login')
|
|
563
|
+
.set('x-client-ssaid', ssaid2)
|
|
564
|
+
.send({ email: email2, password: password2 })
|
|
565
|
+
.expect(200);
|
|
566
|
+
|
|
567
|
+
expectOkEnvelope(res.body);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('login with mobile + new password should succeed', async () => {
|
|
571
|
+
const res = await request(httpServer)
|
|
572
|
+
.post('/login')
|
|
573
|
+
.set('x-client-ssaid', ssaid2)
|
|
574
|
+
.send({ mobile: mobile1, password: password2 })
|
|
575
|
+
.expect(200);
|
|
576
|
+
|
|
577
|
+
expectOkEnvelope(res.body);
|
|
578
|
+
token = (
|
|
579
|
+
res.body as GenericReturnMessageDto<LoginResponseDto>
|
|
580
|
+
).data.token;
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// ──────────────────────────────────────────────────────────
|
|
585
|
+
// 10. Unregister dual-contact user
|
|
586
|
+
// ──────────────────────────────────────────────────────────
|
|
587
|
+
|
|
588
|
+
describe('Unregister dual-contact user', () => {
|
|
589
|
+
it('POST /user-center/unregister -> should revoke session', async () => {
|
|
590
|
+
const res = await request(httpServer)
|
|
591
|
+
.post('/user-center/unregister')
|
|
592
|
+
.set('x-client-ssaid', ssaid2)
|
|
593
|
+
.set('x-client-token', token)
|
|
594
|
+
.expect(200);
|
|
595
|
+
|
|
596
|
+
expectOkEnvelope(res.body);
|
|
597
|
+
|
|
598
|
+
const me = await request(httpServer)
|
|
599
|
+
.get('/user-center/me')
|
|
600
|
+
.set('x-client-ssaid', ssaid2)
|
|
601
|
+
.set('x-client-token', token);
|
|
602
|
+
|
|
603
|
+
expect(me.status).toBe(401);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('POST /login/unregister-with-code (email) -> should succeed on already unregistered', async () => {
|
|
607
|
+
await request(httpServer)
|
|
608
|
+
.post('/send-code/send')
|
|
609
|
+
.set('x-client-ssaid', ssaid2)
|
|
610
|
+
.send({ email: email2, codePurpose: 'Unregister' })
|
|
611
|
+
.expect(200);
|
|
612
|
+
|
|
613
|
+
const res = await request(httpServer)
|
|
614
|
+
.post('/login/unregister-with-code')
|
|
615
|
+
.send({ email: email2, code: EMAIL_CODE })
|
|
616
|
+
.expect(200);
|
|
617
|
+
|
|
618
|
+
expectOkEnvelope(res.body);
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
});
|
package/test/app-e2e.spec.ts
CHANGED
|
@@ -171,6 +171,9 @@ describe('SimpleUserModule (e2e)', () => {
|
|
|
171
171
|
.data;
|
|
172
172
|
expect(data.token).toHaveLength(64);
|
|
173
173
|
|
|
174
|
+
// changePassword kicks all sessions, so update token
|
|
175
|
+
token = data.token;
|
|
176
|
+
|
|
174
177
|
// 用错密码应 403
|
|
175
178
|
const loginBad = await request(httpServer)
|
|
176
179
|
.post('/login')
|