@@ -20,6 +20,7 @@ import { StatsDService } from '@fxa/shared/metrics/statsd';
2020import { MessageStatus } from 'twilio/lib/rest/api/v2010/account/message' ;
2121import { TwilioConfig } from './twilio.config' ;
2222import { getExpectedTwilioSignature } from 'twilio/lib/webhooks/webhooks' ;
23+ import { SegmentedMessage } from 'sms-segments-calculator' ;
2324
2425describe ( 'RecoveryPhoneService' , ( ) => {
2526 const phoneNumber = '+15005551234' ;
@@ -28,6 +29,7 @@ describe('RecoveryPhoneService', () => {
2829
2930 const mockLogger = {
3031 error : jest . fn ( ) ,
32+ warn : jest . fn ( ) ,
3133 } ;
3234
3335 const mockMetrics = {
@@ -39,6 +41,7 @@ describe('RecoveryPhoneService', () => {
3941 sendSMS : jest . fn ( ) ,
4042 phoneNumberLookup : jest . fn ( ) ,
4143 messageStatus : jest . fn ( ) ,
44+ checkMessageSegments : jest . fn ( ) ,
4245 } ;
4346
4447 const mockRecoveryPhoneManager = {
@@ -74,12 +77,27 @@ describe('RecoveryPhoneService', () => {
7477 validateWebhookCalls : true ,
7578 } satisfies TwilioConfig ;
7679
80+ const mockGetFormattedMessage = async ( code : string ) => {
81+ return {
82+ msg : `${ code } is your Mozilla verification code. Expires in 5 minutes.` ,
83+ shortMsg : `Mozilla verification code: ${ code } ` ,
84+ failsafeMsg : `Mozilla: ${ code } ` ,
85+ } ;
86+ } ;
87+
7788 const mockError = new Error ( 'BOOM' ) ;
7889
7990 let service : RecoveryPhoneService ;
8091
8192 beforeEach ( async ( ) => {
8293 mockSmsManager . sendSMS . mockReturnValue ( { status : 'success' } ) ;
94+ mockSmsManager . checkMessageSegments . mockImplementation ( ( msg : string ) => {
95+ const sm = new SegmentedMessage ( msg ) ;
96+ return {
97+ overLimit : sm . segmentsCount > 1 ,
98+ segmentedMessage : sm ,
99+ } ;
100+ } ) ;
83101 mockRecoveryPhoneManager . hasRecoveryCodes . mockResolvedValue ( true ) ;
84102 mockRecoveryPhoneManager . getAllUnconfirmed . mockResolvedValue ( [ ] ) ;
85103
@@ -119,16 +137,20 @@ describe('RecoveryPhoneService', () => {
119137 expect ( service ) . toBeInstanceOf ( RecoveryPhoneService ) ;
120138 } ) ;
121139
122- it ( 'Should set up a phone number' , async ( ) => {
140+ it ( 'Should setup a phone number' , async ( ) => {
123141 mockOtpManager . generateCode . mockReturnValue ( code ) ;
124142
125- const result = await service . setupPhoneNumber ( uid , phoneNumber ) ;
143+ const result = await service . setupPhoneNumber (
144+ uid ,
145+ phoneNumber ,
146+ mockGetFormattedMessage
147+ ) ;
126148
127149 expect ( result ) . toBeTruthy ( ) ;
128150 expect ( mockOtpManager . generateCode ) . toBeCalled ( ) ;
129151 expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
130152 to : phoneNumber ,
131- body : code ,
153+ body : ( await mockGetFormattedMessage ( code ) ) . msg ,
132154 } ) ;
133155 expect ( mockRecoveryPhoneManager . storeUnconfirmed ) . toBeCalledWith (
134156 uid ,
@@ -137,6 +159,7 @@ describe('RecoveryPhoneService', () => {
137159 true
138160 ) ;
139161 expect ( mockRecoveryPhoneManager . getAllUnconfirmed ) . toBeCalledWith ( uid ) ;
162+ expect ( result ) . toBeTruthy ( ) ;
140163 } ) ;
141164
142165 it ( 'Should send new code to set up a phone number' , async ( ) => {
@@ -146,15 +169,19 @@ describe('RecoveryPhoneService', () => {
146169 'this:is:the:code456' ,
147170 ] ) ;
148171
149- const result = await service . setupPhoneNumber ( uid , phoneNumber ) ;
172+ const result = await service . setupPhoneNumber (
173+ uid ,
174+ phoneNumber ,
175+ mockGetFormattedMessage
176+ ) ;
150177
151178 expect ( result ) . toBeTruthy ( ) ;
152179 expect ( mockRecoveryPhoneManager . removeCode ) . toBeCalledWith ( uid , 'code123' ) ;
153180 expect ( mockRecoveryPhoneManager . removeCode ) . toBeCalledWith ( uid , 'code456' ) ;
154181 expect ( mockOtpManager . generateCode ) . toBeCalled ( ) ;
155182 expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
156183 to : phoneNumber ,
157- body : code ,
184+ body : ( await mockGetFormattedMessage ( code ) ) . msg ,
158185 } ) ;
159186 expect ( mockRecoveryPhoneManager . storeUnconfirmed ) . toBeCalledWith (
160187 uid ,
@@ -167,19 +194,18 @@ describe('RecoveryPhoneService', () => {
167194
168195 it ( 'handles message template when provided to set up phone number' , async ( ) => {
169196 mockOtpManager . generateCode . mockReturnValue ( code ) ;
170- const getFormattedMessage = jest . fn ( ) . mockResolvedValue ( 'message' ) ;
171197
172198 const result = await service . setupPhoneNumber (
173199 uid ,
174200 phoneNumber ,
175- getFormattedMessage
201+ mockGetFormattedMessage
176202 ) ;
177203
178204 expect ( result ) . toBeTruthy ( ) ;
179205 expect ( mockOtpManager . generateCode ) . toBeCalled ( ) ;
180206 expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
181207 to : phoneNumber ,
182- body : 'message' ,
208+ body : ( await mockGetFormattedMessage ( code ) ) . msg ,
183209 } ) ;
184210 expect ( mockRecoveryPhoneManager . storeUnconfirmed ) . toBeCalledWith (
185211 uid ,
@@ -192,38 +218,38 @@ describe('RecoveryPhoneService', () => {
192218
193219 it ( 'Will reject a phone number that is not part of launch' , async ( ) => {
194220 const to = '+16005551234' ;
195- await expect ( service . setupPhoneNumber ( uid , to ) ) . rejects . toEqual (
196- new RecoveryNumberNotSupportedError ( to )
197- ) ;
221+ await expect (
222+ service . setupPhoneNumber ( uid , to , mockGetFormattedMessage )
223+ ) . rejects . toEqual ( new RecoveryNumberNotSupportedError ( to ) ) ;
198224 } ) ;
199225
200226 it ( 'Will reject a phone number if it has been used for too many accounts' , async ( ) => {
201227 mockRecoveryPhoneManager . getCountByPhoneNumber . mockReturnValue ( 5 ) ;
202228
203- await expect ( service . setupPhoneNumber ( uid , phoneNumber ) ) . rejects . toEqual (
204- new RecoveryPhoneRegistrationLimitReached ( phoneNumber )
205- ) ;
229+ await expect (
230+ service . setupPhoneNumber ( uid , phoneNumber , mockGetFormattedMessage )
231+ ) . rejects . toEqual ( new RecoveryPhoneRegistrationLimitReached ( phoneNumber ) ) ;
206232 } ) ;
207233
208234 it ( 'Throws error during send sms' , async ( ) => {
209235 mockSmsManager . sendSMS . mockRejectedValueOnce ( mockError ) ;
210- await expect ( service . setupPhoneNumber ( uid , phoneNumber ) ) . rejects . toEqual (
211- mockError
212- ) ;
236+ await expect (
237+ service . setupPhoneNumber ( uid , phoneNumber , mockGetFormattedMessage )
238+ ) . rejects . toEqual ( mockError ) ;
213239 } ) ;
214240
215241 it ( 'Throws error during otp code creation' , async ( ) => {
216242 mockOtpManager . generateCode . mockRejectedValueOnce ( mockError ) ;
217- await expect ( service . setupPhoneNumber ( uid , phoneNumber ) ) . rejects . toEqual (
218- mockError
219- ) ;
243+ await expect (
244+ service . setupPhoneNumber ( uid , phoneNumber , mockGetFormattedMessage )
245+ ) . rejects . toEqual ( mockError ) ;
220246 } ) ;
221247
222248 it ( 'throws error during storing of unconfirmed number' , async ( ) => {
223249 mockRecoveryPhoneManager . storeUnconfirmed . mockRejectedValueOnce ( mockError ) ;
224- await expect ( service . setupPhoneNumber ( uid , phoneNumber ) ) . rejects . toEqual (
225- mockError
226- ) ;
250+ await expect (
251+ service . setupPhoneNumber ( uid , phoneNumber , mockGetFormattedMessage )
252+ ) . rejects . toEqual ( mockError ) ;
227253 } ) ;
228254
229255 describe ( 'has confirmed code' , ( ) => {
@@ -458,7 +484,7 @@ describe('RecoveryPhoneService', () => {
458484 mockOtpManager . generateCode . mockResolvedValueOnce ( code ) ;
459485 mockSmsManager . sendSMS . mockResolvedValue ( { status : 'success' } ) ;
460486
461- const result = await service . sendCode ( uid ) ;
487+ const result = await service . sendCode ( uid , mockGetFormattedMessage ) ;
462488
463489 expect ( result ) . toBeTruthy ( ) ;
464490 expect ( mockRecoveryPhoneManager . getConfirmedPhoneNumber ) . toBeCalledWith (
@@ -473,7 +499,47 @@ describe('RecoveryPhoneService', () => {
473499 expect ( mockOtpManager . generateCode ) . toBeCalled ( ) ;
474500 expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
475501 to : phoneNumber ,
476- body : code ,
502+ body : ( await mockGetFormattedMessage ( code ) ) . msg ,
503+ } ) ;
504+ } ) ;
505+
506+ it ( 'should fallback to the short message' , async ( ) => {
507+ mockRecoveryPhoneManager . getConfirmedPhoneNumber . mockResolvedValueOnce ( {
508+ phoneNumber,
509+ } ) ;
510+ mockOtpManager . generateCode . mockResolvedValueOnce ( code ) ;
511+ mockSmsManager . sendSMS . mockResolvedValue ( { status : 'success' } ) ;
512+
513+ const result = await service . sendCode ( uid , async ( ) => ( {
514+ msg : new Array ( 161 ) . fill ( 'z' ) . join ( '' ) ,
515+ shortMsg : `Your Mozilla Account code is ${ code } ` ,
516+ failsafeMsg : `${ code } ` ,
517+ } ) ) ;
518+
519+ expect ( result ) . toBeTruthy ( ) ;
520+ expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
521+ to : phoneNumber ,
522+ body : `Your Mozilla Account code is ${ code } ` ,
523+ } ) ;
524+ } ) ;
525+
526+ it ( 'should fallback to the fail safe message' , async ( ) => {
527+ mockRecoveryPhoneManager . getConfirmedPhoneNumber . mockResolvedValueOnce ( {
528+ phoneNumber,
529+ } ) ;
530+ mockOtpManager . generateCode . mockResolvedValueOnce ( code ) ;
531+ mockSmsManager . sendSMS . mockResolvedValue ( { status : 'success' } ) ;
532+
533+ const result = await service . sendCode ( uid , async ( ) => ( {
534+ msg : new Array ( 161 ) . fill ( 'z' ) . join ( '' ) ,
535+ shortMsg : new Array ( 161 ) . fill ( 'z' ) . join ( '' ) ,
536+ failsafeMsg : `Failsafe: ${ code } ` ,
537+ } ) ) ;
538+
539+ expect ( result ) . toBeTruthy ( ) ;
540+ expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
541+ to : phoneNumber ,
542+ body : `Failsafe: ${ code } ` ,
477543 } ) ;
478544 } ) ;
479545
@@ -484,15 +550,17 @@ describe('RecoveryPhoneService', () => {
484550 mockOtpManager . generateCode . mockResolvedValueOnce ( code ) ;
485551 mockSmsManager . sendSMS . mockResolvedValue ( { status : 'failed' } ) ;
486552
487- const result = await service . sendCode ( uid ) ;
553+ const result = await service . sendCode ( uid , mockGetFormattedMessage ) ;
488554
489555 expect ( result ) . toBeFalsy ( ) ;
490556 } ) ;
491557
492558 it ( 'should throw error if recovery phone number does not exist' , async ( ) => {
493559 const error = new RecoveryNumberNotExistsError ( uid ) ;
494560 mockRecoveryPhoneManager . getConfirmedPhoneNumber . mockRejectedValue ( error ) ;
495- expect ( service . sendCode ( uid ) ) . rejects . toThrow ( error ) ;
561+ expect ( service . sendCode ( uid , mockGetFormattedMessage ) ) . rejects . toThrow (
562+ error
563+ ) ;
496564 } ) ;
497565
498566 it ( 'Should send new code for setup phone number' , async ( ) => {
@@ -508,7 +576,7 @@ describe('RecoveryPhoneService', () => {
508576 mockOtpManager . generateCode . mockResolvedValueOnce ( code ) ;
509577 mockSmsManager . sendSMS . mockResolvedValue ( { status : 'success' } ) ;
510578
511- const result = await service . sendCode ( uid ) ;
579+ const result = await service . sendCode ( uid , mockGetFormattedMessage ) ;
512580
513581 expect ( result ) . toBeTruthy ( ) ;
514582 expect ( mockRecoveryPhoneManager . getConfirmedPhoneNumber ) . toBeCalledWith (
@@ -523,7 +591,7 @@ describe('RecoveryPhoneService', () => {
523591 expect ( mockOtpManager . generateCode ) . toBeCalled ( ) ;
524592 expect ( mockSmsManager . sendSMS ) . toBeCalledWith ( {
525593 to : phoneNumber ,
526- body : code ,
594+ body : ( await mockGetFormattedMessage ( code ) ) . msg ,
527595 } ) ;
528596
529597 expect ( mockRecoveryPhoneManager . removeCode ) . toBeCalledWith (
@@ -588,12 +656,12 @@ describe('RecoveryPhoneService', () => {
588656 expect ( service . confirmSigninCode ( uid , '000000' ) ) . rejects . toEqual (
589657 new RecoveryPhoneNotEnabled ( )
590658 ) ;
591- expect ( service . sendCode ( uid ) ) . rejects . toEqual (
592- new RecoveryPhoneNotEnabled ( )
593- ) ;
594- expect ( service . setupPhoneNumber ( uid , '+15550005555' ) ) . rejects . toEqual (
659+ expect ( service . sendCode ( uid , mockGetFormattedMessage ) ) . rejects . toEqual (
595660 new RecoveryPhoneNotEnabled ( )
596661 ) ;
662+ expect (
663+ service . setupPhoneNumber ( uid , '+15550005555' , mockGetFormattedMessage )
664+ ) . rejects . toEqual ( new RecoveryPhoneNotEnabled ( ) ) ;
597665 } ) ;
598666 } ) ;
599667
0 commit comments