1
1
import {
2
2
PhoneNumberLookupData ,
3
+ RECOVERY_PHONE_REDIS_PREFIX ,
3
4
RecoveryPhoneManager ,
4
5
} from './recovery-phone.manager' ;
5
6
import {
@@ -9,10 +10,12 @@ import {
9
10
RecoveryPhoneFactory ,
10
11
} from '@fxa/shared/db/mysql/account' ;
11
12
import { Test } from '@nestjs/testing' ;
13
+ import Redis from 'ioredis' ;
12
14
13
15
describe ( 'RecoveryPhoneManager' , ( ) => {
14
16
let recoveryPhoneManager : RecoveryPhoneManager ;
15
17
let db : AccountDatabase ;
18
+ let redis : Redis . Redis ;
16
19
const dateMock = jest . spyOn ( global . Date , 'now' ) ;
17
20
18
21
// Taken from: https://www.twilio.com/docs/lookup/v2-api#code-lookup-with-data-packages
@@ -38,19 +41,29 @@ describe('RecoveryPhoneManager', () => {
38
41
callForwarding : { } ,
39
42
} ;
40
43
41
- const mockRedis = {
42
- set : jest . fn ( ) ,
43
- get : jest . fn ( ) ,
44
- del : jest . fn ( ) ,
45
- } ;
44
+ async function clearRedisSmsKeys ( ) {
45
+ const addedKeys = await redis . keys ( RECOVERY_PHONE_REDIS_PREFIX ) ;
46
+ for ( const key of addedKeys ) {
47
+ await redis . del ( key ) ;
48
+ }
49
+ }
46
50
47
51
beforeAll ( async ( ) => {
48
52
dateMock . mockImplementation ( ( ) => 1739227529776 ) ;
53
+
49
54
db = await testAccountDatabaseSetup ( [
50
55
'accounts' ,
51
56
'recoveryPhones' ,
52
57
'recoveryCodes' ,
53
58
] ) ;
59
+
60
+ // Since this is an integration we can expect that
61
+ // infrastructure is running.
62
+ redis = new Redis ( 'localhost' ) ;
63
+
64
+ // Make sure sms key state is clean
65
+ await clearRedisSmsKeys ( ) ;
66
+
54
67
const moduleRef = await Test . createTestingModule ( {
55
68
providers : [
56
69
RecoveryPhoneManager ,
@@ -60,7 +73,7 @@ describe('RecoveryPhoneManager', () => {
60
73
} ,
61
74
{
62
75
provide : 'RecoveryPhoneRedis' ,
63
- useValue : mockRedis ,
76
+ useValue : redis ,
64
77
} ,
65
78
] ,
66
79
} ) . compile ( ) ;
@@ -69,6 +82,7 @@ describe('RecoveryPhoneManager', () => {
69
82
} ) ;
70
83
71
84
afterAll ( async ( ) => {
85
+ await clearRedisSmsKeys ( ) ;
72
86
await db . destroy ( ) ;
73
87
dateMock . mockReset ( ) ;
74
88
} ) ;
@@ -192,48 +206,63 @@ describe('RecoveryPhoneManager', () => {
192
206
insertIntoSpy . mockRestore ( ) ;
193
207
} ) ;
194
208
195
- it ( 'should store unconfirmed phone number data in Redis' , async ( ) => {
209
+ it ( 'should store and retrieve unconfirmed phone number data in Redis' , async ( ) => {
196
210
const mockPhone = RecoveryPhoneFactory ( ) ;
197
211
const { uid, phoneNumber } = mockPhone ;
198
- const code = '123456' ;
199
212
const isSetup = true ;
200
213
await recoveryPhoneManager . storeUnconfirmed (
201
214
uid . toString ( 'hex' ) ,
202
- code ,
215
+ '111111' ,
203
216
phoneNumber ,
204
217
isSetup ,
205
218
mockLookUpData
206
219
) ;
207
-
208
- const redisKey = `sms-attempt:${ uid . toString ( 'hex' ) } :${ code } ` ;
209
-
210
- expect ( mockRedis . set ) . toHaveBeenCalledWith (
211
- redisKey ,
212
- expect . any ( String ) ,
213
- 'EX' ,
214
- 600
220
+ await recoveryPhoneManager . storeUnconfirmed (
221
+ uid . toString ( 'hex' ) ,
222
+ '222222' ,
223
+ phoneNumber ,
224
+ isSetup
215
225
) ;
216
226
217
- const expectedData = expect . objectContaining ( {
218
- createdAt : expect . any ( Number ) ,
227
+ // Store one item for different accounts to make sure lookup is filtering
228
+ // on uid
229
+ await recoveryPhoneManager . storeUnconfirmed (
230
+ uid . toString ( 'hex' ) . replace ( / ./ g, '1' ) ,
231
+ '333333' ,
219
232
phoneNumber ,
220
- isSetup,
221
- lookupData : JSON . stringify ( mockLookUpData ) ,
222
- } ) ;
233
+ isSetup
234
+ ) ;
235
+
236
+ const firstUnconfirmedCode = await recoveryPhoneManager . getUnconfirmed (
237
+ uid . toString ( 'hex' ) ,
238
+ '111111'
239
+ ) ;
240
+ expect ( firstUnconfirmedCode ) . toBeDefined ( ) ;
241
+ expect ( firstUnconfirmedCode ?. lookupData ) . toBeDefined ( ) ;
242
+ expect ( firstUnconfirmedCode ?. lookupData ) . toEqual (
243
+ JSON . stringify ( mockLookUpData )
244
+ ) ;
223
245
224
- const storedData = mockRedis . set . mock . calls [ 0 ] [ 1 ] ;
225
- expect ( ( ) => JSON . parse ( storedData ) ) . not . toThrow ( ) ;
226
- const parsedData = JSON . parse ( mockRedis . set . mock . calls [ 0 ] [ 1 ] ) ;
227
- expect ( parsedData ) . toEqual ( expectedData ) ;
246
+ const secondUnconfirmedCode = await recoveryPhoneManager . getUnconfirmed (
247
+ uid . toString ( 'hex' ) ,
248
+ '222222'
249
+ ) ;
250
+ expect ( secondUnconfirmedCode ) . toBeDefined ( ) ;
251
+ expect ( secondUnconfirmedCode ?. lookupData ) . toEqual ( null ) ;
252
+
253
+ const allUnconfirmedCodes =
254
+ await recoveryPhoneManager . getAllUnconfirmedCodes ( uid . toString ( 'hex' ) ) ;
255
+ expect ( allUnconfirmedCodes . length ) . toEqual ( 2 ) ;
256
+ expect ( allUnconfirmedCodes ) . toContain ( '111111' ) ;
257
+ expect ( allUnconfirmedCodes ) . toContain ( '222222' ) ;
258
+ expect ( allUnconfirmedCodes ) . not . toContain ( '333333' ) ;
228
259
} ) ;
229
260
230
261
it ( 'should return null if no unconfirmed phone number data is found in Redis' , async ( ) => {
231
262
const mockPhone = RecoveryPhoneFactory ( ) ;
232
263
const { uid } = mockPhone ;
233
264
const code = '123456' ;
234
265
235
- mockRedis . get . mockResolvedValue ( null ) ;
236
-
237
266
const result = await recoveryPhoneManager . getUnconfirmed (
238
267
uid . toString ( 'hex' ) ,
239
268
code
0 commit comments