@@ -90,6 +90,7 @@ function mockSyncDesktopV3Integration() {
9090 data : { service : 'sync' } ,
9191 isDesktopSync : ( ) => true ,
9292 isFirefoxClientServiceRelay : ( ) => false ,
93+ isFirefoxNonSync : ( ) => false ,
9394 getCmsInfo : ( ) => undefined ,
9495 } as Integration ;
9596}
@@ -105,6 +106,7 @@ function mockOAuthWebIntegration(
105106 data,
106107 isDesktopSync : ( ) => false ,
107108 isFirefoxClientServiceRelay : ( ) => false ,
109+ isFirefoxNonSync : ( ) => false ,
108110 getCmsInfo : ( ) => undefined ,
109111 } as Integration ;
110112}
@@ -118,6 +120,7 @@ function mockOAuthNativeIntegration() {
118120 wantsKeys : ( ) => true ,
119121 isDesktopSync : ( ) => true ,
120122 isFirefoxClientServiceRelay : ( ) => false ,
123+ isFirefoxNonSync : ( ) => false ,
121124 getCmsInfo : ( ) => undefined ,
122125 data : {
123126 service : 'sync' ,
@@ -126,6 +129,23 @@ function mockOAuthNativeIntegration() {
126129 } as Integration ;
127130}
128131
132+ function mockFirefoxNonSyncIntegration ( ) {
133+ integration = {
134+ type : IntegrationType . OAuthNative ,
135+ getService : ( ) => ModelsModule . OAuthNativeServices . Relay ,
136+ getClientId : ( ) => MOCK_CLIENT_ID ,
137+ isSync : ( ) => false ,
138+ wantsKeys : ( ) => false ,
139+ isDesktopSync : ( ) => false ,
140+ isFirefoxClientServiceRelay : ( ) => true ,
141+ isFirefoxNonSync : ( ) => true ,
142+ getCmsInfo : ( ) => undefined ,
143+ data : {
144+ service : ModelsModule . OAuthNativeServices . Relay ,
145+ } ,
146+ } as Integration ;
147+ }
148+
129149function mockWebIntegration ( ) {
130150 // Leaving for historical record. Remove once baked.
131151 // integration = {
@@ -157,7 +177,11 @@ function mockWebIntegration() {
157177function mockFetchModule ( ) {
158178 global . fetch = jest . fn ( ) . mockResolvedValue ( {
159179 ok : true ,
160- json : ( ) => Promise . resolve ( { id : 'avatar-id' , url : 'https://example.com/avatar.png' } ) ,
180+ json : ( ) =>
181+ Promise . resolve ( {
182+ id : 'avatar-id' ,
183+ url : 'https://example.com/avatar.png' ,
184+ } ) ,
161185 } ) ;
162186}
163187
@@ -201,6 +225,7 @@ mockSensitiveDataClient.setDataType = jest.fn();
201225
202226const mockSession = {
203227 isSessionVerified : jest . fn ( ) . mockResolvedValue ( true ) ,
228+ isValid : jest . fn ( ) . mockResolvedValue ( true ) ,
204229 sendVerificationCode : jest . fn ( ) . mockResolvedValue ( undefined ) ,
205230 verified : false ,
206231 token : MOCK_SESSION_TOKEN ,
@@ -280,6 +305,7 @@ function mockModelsModule() {
280305 } ) ) ;
281306 ( ModelsModule . useSession as jest . Mock ) . mockImplementation ( ( ) => mockSession ) ;
282307 mockSession . isSessionVerified = jest . fn ( ) . mockResolvedValue ( true ) ;
308+ mockSession . isValid = jest . fn ( ) . mockResolvedValue ( true ) ;
283309 mockSession . sendVerificationCode = jest . fn ( ) . mockResolvedValue ( undefined ) ;
284310}
285311
@@ -410,9 +436,9 @@ function mockSentryModule() {
410436 mockSentryCaptureMessage = jest . spyOn ( SentryModule , 'captureMessage' ) ;
411437}
412438
413- function render (
414- options ?: { useFxAStatusResult ?: ReturnType < typeof mockUseFxAStatus > }
415- ) {
439+ function render ( options ?: {
440+ useFxAStatusResult ?: ReturnType < typeof mockUseFxAStatus > ;
441+ } ) {
416442 const useFxAStatusResult = options ?. useFxAStatusResult || mockUseFxAStatus ( ) ;
417443
418444 return renderWithLocalizationProvider (
@@ -940,10 +966,12 @@ describe('signin container', () => {
940966 keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
941967 } ) ;
942968 // Mock passwordChangeStartWithAuthPW to succeed
943- mockAuthClient . passwordChangeStartWithAuthPW = jest . fn ( ) . mockResolvedValue ( {
944- keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
945- passwordChangeToken : 'mockPasswordChangeToken' ,
946- } ) ;
969+ mockAuthClient . passwordChangeStartWithAuthPW = jest
970+ . fn ( )
971+ . mockResolvedValue ( {
972+ keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
973+ passwordChangeToken : 'mockPasswordChangeToken' ,
974+ } ) ;
947975 // Mock wrappedAccountKeys to succeed
948976 mockAuthClient . wrappedAccountKeys = jest . fn ( ) . mockResolvedValue ( {
949977 kA : MOCK_KB ,
@@ -1034,10 +1062,12 @@ describe('signin container', () => {
10341062 keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
10351063 } ) ;
10361064 // Mock passwordChangeStartWithAuthPW to throw an error
1037- mockAuthClient . passwordChangeStartWithAuthPW = jest . fn ( ) . mockRejectedValue ( {
1038- errno : 999 ,
1039- message : 'Test error' ,
1040- } ) ;
1065+ mockAuthClient . passwordChangeStartWithAuthPW = jest
1066+ . fn ( )
1067+ . mockRejectedValue ( {
1068+ errno : 999 ,
1069+ message : 'Test error' ,
1070+ } ) ;
10411071
10421072 render ( ) ;
10431073
@@ -1075,10 +1105,12 @@ describe('signin container', () => {
10751105 keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
10761106 } ) ;
10771107 // Mock passwordChangeStartWithAuthPW to succeed
1078- mockAuthClient . passwordChangeStartWithAuthPW = jest . fn ( ) . mockResolvedValue ( {
1079- keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
1080- passwordChangeToken : 'mockPasswordChangeToken' ,
1081- } ) ;
1108+ mockAuthClient . passwordChangeStartWithAuthPW = jest
1109+ . fn ( )
1110+ . mockResolvedValue ( {
1111+ keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
1112+ passwordChangeToken : 'mockPasswordChangeToken' ,
1113+ } ) ;
10821114 // Mock wrappedAccountKeys to throw an error
10831115 mockAuthClient . wrappedAccountKeys = jest . fn ( ) . mockRejectedValue ( {
10841116 errno : 999 ,
@@ -1122,10 +1154,12 @@ describe('signin container', () => {
11221154 keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
11231155 } ) ;
11241156 // Mock passwordChangeStartWithAuthPW to succeed
1125- mockAuthClient . passwordChangeStartWithAuthPW = jest . fn ( ) . mockResolvedValue ( {
1126- keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
1127- passwordChangeToken : 'mockPasswordChangeToken' ,
1128- } ) ;
1157+ mockAuthClient . passwordChangeStartWithAuthPW = jest
1158+ . fn ( )
1159+ . mockResolvedValue ( {
1160+ keyFetchToken : MOCK_KEY_FETCH_TOKEN ,
1161+ passwordChangeToken : 'mockPasswordChangeToken' ,
1162+ } ) ;
11291163 // Mock wrappedAccountKeys to succeed
11301164 mockAuthClient . wrappedAccountKeys = jest . fn ( ) . mockResolvedValue ( {
11311165 kA : MOCK_KB ,
@@ -1385,6 +1419,98 @@ describe('signin container', () => {
13851419 } ) ;
13861420 } ) ;
13871421
1422+ describe ( 'Firefox non-Sync session validation' , ( ) => {
1423+ beforeEach ( ( ) => {
1424+ mockFirefoxNonSyncIntegration ( ) ;
1425+ } ) ;
1426+
1427+ it ( 'calls session.isValid and renders signin when session is valid' , async ( ) => {
1428+ const storedAccount = {
1429+ ...MOCK_STORED_ACCOUNT ,
1430+ email : MOCK_QUERY_PARAM_EMAIL ,
1431+ sessionToken : MOCK_SESSION_TOKEN ,
1432+ } ;
1433+ mockCurrentAccount ( storedAccount ) ;
1434+ mockUseValidateModule ( ) ;
1435+ mockSession . isValid = jest . fn ( ) . mockResolvedValue ( true ) ;
1436+
1437+ render ( ) ;
1438+
1439+ await waitFor ( ( ) => {
1440+ expect ( mockSession . isValid ) . toHaveBeenCalledWith ( MOCK_SESSION_TOKEN ) ;
1441+ } ) ;
1442+ await waitFor ( ( ) => {
1443+ expect ( currentSigninProps ) . toBeDefined ( ) ;
1444+ expect ( currentSigninProps ?. sessionToken ) . toBe ( MOCK_SESSION_TOKEN ) ;
1445+ } ) ;
1446+ expect ( CacheModule . discardSessionToken ) . not . toHaveBeenCalled ( ) ;
1447+ } ) ;
1448+
1449+ it ( 'calls session.isValid and discards token when session is invalid' , async ( ) => {
1450+ const storedAccount : { sessionToken ?: string ; [ key : string ] : any } = {
1451+ ...MOCK_STORED_ACCOUNT ,
1452+ email : MOCK_QUERY_PARAM_EMAIL ,
1453+ sessionToken : MOCK_SESSION_TOKEN ,
1454+ } ;
1455+ mockCurrentAccount ( storedAccount ) ;
1456+ // Make the spy actually clear the token so re-render picks it up
1457+ jest . spyOn ( CacheModule , 'discardSessionToken' ) . mockImplementation ( ( ) => {
1458+ storedAccount . sessionToken = undefined ;
1459+ } ) ;
1460+ mockUseValidateModule ( ) ;
1461+ mockSession . isValid = jest . fn ( ) . mockResolvedValue ( false ) ;
1462+
1463+ render ( ) ;
1464+
1465+ await waitFor ( ( ) => {
1466+ expect ( mockSession . isValid ) . toHaveBeenCalledWith ( MOCK_SESSION_TOKEN ) ;
1467+ } ) ;
1468+ await waitFor ( ( ) => {
1469+ expect ( CacheModule . discardSessionToken ) . toHaveBeenCalled ( ) ;
1470+ } ) ;
1471+ await waitFor ( ( ) => {
1472+ expect ( currentSigninProps ) . toBeDefined ( ) ;
1473+ expect ( currentSigninProps ?. sessionToken ) . toBeUndefined ( ) ;
1474+ } ) ;
1475+ } ) ;
1476+
1477+ it ( 'does not call session.isValid for Sync integration' , async ( ) => {
1478+ mockOAuthNativeIntegration ( ) ;
1479+ mockCurrentAccount ( {
1480+ ...MOCK_STORED_ACCOUNT ,
1481+ email : MOCK_QUERY_PARAM_EMAIL ,
1482+ sessionToken : MOCK_SESSION_TOKEN ,
1483+ } ) ;
1484+ mockUseValidateModule ( ) ;
1485+ mockLocationState = MOCK_LOCATION_STATE_CAN_LINK_ACCOUNT_OK ;
1486+ ( ensureCanLinkAcountOrRedirect as jest . Mock ) . mockResolvedValue ( true ) ;
1487+
1488+ render ( ) ;
1489+
1490+ await waitFor ( ( ) => {
1491+ expect ( currentSigninProps ) . toBeDefined ( ) ;
1492+ } ) ;
1493+ expect ( mockSession . isValid ) . not . toHaveBeenCalled ( ) ;
1494+ } ) ;
1495+
1496+ it ( 'does not call session.isValid for OAuth web integration' , async ( ) => {
1497+ mockOAuthWebIntegration ( ) ;
1498+ mockCurrentAccount ( {
1499+ ...MOCK_STORED_ACCOUNT ,
1500+ email : MOCK_QUERY_PARAM_EMAIL ,
1501+ sessionToken : MOCK_SESSION_TOKEN ,
1502+ } ) ;
1503+ mockUseValidateModule ( ) ;
1504+
1505+ render ( ) ;
1506+
1507+ await waitFor ( ( ) => {
1508+ expect ( currentSigninProps ) . toBeDefined ( ) ;
1509+ } ) ;
1510+ expect ( mockSession . isValid ) . not . toHaveBeenCalled ( ) ;
1511+ } ) ;
1512+ } ) ;
1513+
13881514 /**
13891515 * These tests trigger the `setAccountStatus` useEffect, and because of the IIFE
13901516 * inside that, and because there are no `waitFor` calls, they need to explicitly await
0 commit comments