22 * License, v. 2.0. If a copy of the MPL was not distributed with this
33 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
5+ import fs from 'fs' ;
6+ import path from 'path' ;
57import {
68 categorizeWebAuthnError ,
9+ ERROR_MAP ,
710 handleWebAuthnError ,
811 WebAuthnErrorCategory ,
912 WebAuthnErrorType ,
@@ -30,8 +33,9 @@ describe('categorizeWebAuthnError — user-action errors (no Sentry)', () => {
3033 expect ( result . category ) . toBe ( WebAuthnErrorCategory . UserAction ) ;
3134 expect ( result . errorType ) . toBe ( errorType ) ;
3235 expect ( result . logToSentry ) . toBe ( false ) ;
33- expect ( result . userMessageKey ) . toContain ( operation ) ;
34- expect ( result . userMessageKey ) . toMatch ( / p a s s k e y - .+ - e r r o r - .+ / ) ;
36+ expect ( result . ftlId ) . toContain ( operation ) ;
37+ expect ( result . ftlId ) . toMatch ( / p a s s k e y - .+ - e r r o r - .+ / ) ;
38+ expect ( result . fallbackText ) . toMatch ( / p a s s k e y / i) ;
3539 }
3640 ) ;
3741 } ) ;
@@ -42,9 +46,10 @@ describe('categorizeWebAuthnError — user-action errors (no Sentry)', () => {
4246 dom ( 'NotAllowedError' ) ,
4347 'authentication'
4448 ) ;
45- expect ( reg . userMessageKey ) . not . toBe ( auth . userMessageKey ) ;
46- expect ( reg . userMessageKey ) . toContain ( 'registration' ) ;
47- expect ( auth . userMessageKey ) . toContain ( 'authentication' ) ;
49+ expect ( reg . ftlId ) . not . toBe ( auth . ftlId ) ;
50+ expect ( reg . ftlId ) . toContain ( 'registration' ) ;
51+ expect ( auth . ftlId ) . toContain ( 'authentication' ) ;
52+ expect ( reg . fallbackText ) . not . toBe ( auth . fallbackText ) ;
4853 } ) ;
4954} ) ;
5055
@@ -64,6 +69,10 @@ describe('categorizeWebAuthnError — device/platform errors (no Sentry)', () =>
6469 expect ( result . category ) . toBe ( WebAuthnErrorCategory . DevicePlatform ) ;
6570 expect ( result . errorType ) . toBe ( errorType ) ;
6671 expect ( result . logToSentry ) . toBe ( false ) ;
72+ expect ( result . fallbackText . length ) . toBeGreaterThan ( 0 ) ;
73+ expect ( result . fallbackText ) . toMatch (
74+ / p a s s k e y | a u t h e n t i c a t o r | s e c u r e s i t e / i
75+ ) ;
6776 }
6877 ) ;
6978 } ) ;
@@ -77,8 +86,8 @@ describe('categorizeWebAuthnError — device/platform errors (no Sentry)', () =>
7786 dom ( 'InvalidStateError' ) ,
7887 'authentication'
7988 ) ;
80- expect ( reg . userMessageKey ) . toContain ( 'registration' ) ;
81- expect ( auth . userMessageKey ) . toContain ( 'authentication' ) ;
89+ expect ( reg . ftlId ) . toContain ( 'registration' ) ;
90+ expect ( auth . ftlId ) . toContain ( 'authentication' ) ;
8291 } ) ;
8392} ) ;
8493
@@ -123,7 +132,8 @@ describe('categorizeWebAuthnError — unexpected errors (Sentry enabled)', () =>
123132 expect ( result . category ) . toBe ( WebAuthnErrorCategory . Unexpected ) ;
124133 expect ( result . errorType ) . toBe ( WebAuthnErrorType . Unknown ) ;
125134 expect ( result . logToSentry ) . toBe ( true ) ;
126- expect ( result . userMessageKey ) . toContain ( operation ) ;
135+ expect ( result . ftlId ) . toContain ( operation ) ;
136+ expect ( result . fallbackText ) . toMatch ( / t r y | w e n t w r o n g / i) ;
127137 }
128138 ) ;
129139
@@ -133,9 +143,9 @@ describe('categorizeWebAuthnError — unexpected errors (Sentry enabled)', () =>
133143 dom ( 'ConstraintError' ) ,
134144 'authentication'
135145 ) ;
136- expect ( reg . userMessageKey ) . toContain ( 'registration' ) ;
137- expect ( reg . userMessageKey ) . toContain ( 'constraint' ) ;
138- expect ( auth . userMessageKey ) . toContain ( 'unexpected' ) ;
146+ expect ( reg . ftlId ) . toContain ( 'registration' ) ;
147+ expect ( reg . ftlId ) . toContain ( 'constraint' ) ;
148+ expect ( auth . ftlId ) . toContain ( 'unexpected' ) ;
139149 } ) ;
140150} ) ;
141151
@@ -163,6 +173,31 @@ describe('categorizeWebAuthnError — non-DOMException inputs default to unexpec
163173 ) ;
164174} ) ;
165175
176+ describe ( 'fallbackText matches FTL strings' , ( ) => {
177+ const ftlPath = path . resolve ( __dirname , 'en.ftl' ) ;
178+ const ftlContent = fs . readFileSync ( ftlPath , 'utf-8' ) ;
179+
180+ const ftlMessages = new Map < string , string > ( ) ;
181+ for ( const line of ftlContent . split ( '\n' ) ) {
182+ const match = line . match (
183+ / ^ ( p a s s k e y - (?: r e g i s t r a t i o n | a u t h e n t i c a t i o n ) - e r r o r - \S + ) \s * = \s * ( .+ ) $ /
184+ ) ;
185+ if ( match ) {
186+ ftlMessages . set ( match [ 1 ] , match [ 2 ] . trim ( ) ) ;
187+ }
188+ }
189+
190+ const entries = Object . entries ( ERROR_MAP ) ;
191+
192+ describe . each ( OPERATIONS ) ( 'operation: %s' , ( operation ) => {
193+ test . each ( entries ) ( '%s fallbackText matches FTL' , ( _ , entry ) => {
194+ const ftlValue = ftlMessages . get ( entry . ftlId [ operation ] ) ;
195+ expect ( ftlValue ) . toBeDefined ( ) ;
196+ expect ( entry . fallbackText [ operation ] ) . toBe ( ftlValue ) ;
197+ } ) ;
198+ } ) ;
199+ } ) ;
200+
166201describe ( 'handleWebAuthnError' , ( ) => {
167202 it ( 'calls captureException for unexpected errors' , ( ) => {
168203 const captureException = jest . fn ( ) ;
0 commit comments