1616 */
1717import '../test/setup' ;
1818import { expect } from 'chai' ;
19- import { stub } from 'sinon' ;
20- import { setTokenAutoRefreshEnabled , initializeAppCheck } from './api' ;
21- import { FAKE_SITE_KEY , getFullApp , getFakeApp } from '../test/util' ;
22- import { getState } from './state' ;
19+ import { match , spy , stub } from 'sinon' ;
20+ import {
21+ setTokenAutoRefreshEnabled ,
22+ initializeAppCheck ,
23+ getToken ,
24+ onTokenChanged
25+ } from './api' ;
26+ import {
27+ FAKE_SITE_KEY ,
28+ getFullApp ,
29+ getFakeApp ,
30+ getFakeGreCAPTCHA ,
31+ getFakeAppCheck ,
32+ getFakePlatformLoggingProvider ,
33+ removegreCAPTCHAScriptsOnPage
34+ } from '../test/util' ;
35+ import { clearState , getState } from './state' ;
2336import * as reCAPTCHA from './recaptcha' ;
37+ import * as util from './util' ;
38+ import * as logger from './logger' ;
39+ import * as client from './client' ;
40+ import * as storage from './storage' ;
41+ import * as internalApi from './internal-api' ;
2442import { deleteApp , FirebaseApp } from '@firebase/app-exp' ;
2543import { ReCaptchaV3Provider } from './providers' ;
2644
@@ -29,9 +47,12 @@ describe('api', () => {
2947
3048 beforeEach ( ( ) => {
3149 app = getFullApp ( ) ;
50+ stub ( util , 'getRecaptcha' ) . returns ( getFakeGreCAPTCHA ( ) ) ;
3251 } ) ;
3352
3453 afterEach ( ( ) => {
54+ clearState ( ) ;
55+ removegreCAPTCHAScriptsOnPage ( ) ;
3556 return deleteApp ( app ) ;
3657 } ) ;
3758
@@ -88,8 +109,167 @@ describe('api', () => {
88109 describe ( 'setTokenAutoRefreshEnabled()' , ( ) => {
89110 it ( 'sets isTokenAutoRefreshEnabled correctly' , ( ) => {
90111 const app = getFakeApp ( { automaticDataCollectionEnabled : false } ) ;
91- setTokenAutoRefreshEnabled ( app , true ) ;
112+ const appCheck = getFakeAppCheck ( app ) ;
113+ setTokenAutoRefreshEnabled ( appCheck , true ) ;
92114 expect ( getState ( app ) . isTokenAutoRefreshEnabled ) . to . equal ( true ) ;
93115 } ) ;
94116 } ) ;
117+ describe ( 'getToken()' , ( ) => {
118+ it ( 'getToken() calls the internal getToken() function' , async ( ) => {
119+ const app = getFakeApp ( { automaticDataCollectionEnabled : true } ) ;
120+ const appCheck = getFakeAppCheck ( app ) ;
121+ const internalGetToken = stub ( internalApi , 'getToken' ) . resolves ( {
122+ token : 'a-token-string'
123+ } ) ;
124+ await getToken ( appCheck , true ) ;
125+ expect ( internalGetToken ) . to . be . calledWith (
126+ appCheck . app ,
127+ match . any , // platformLoggerProvider
128+ true
129+ ) ;
130+ } ) ;
131+ it ( 'getToken() throws errors returned with token' , async ( ) => {
132+ const app = getFakeApp ( { automaticDataCollectionEnabled : true } ) ;
133+ const appCheck = getFakeAppCheck ( app ) ;
134+ // If getToken() errors, it returns a dummy token with an error field
135+ // instead of throwing.
136+ stub ( internalApi , 'getToken' ) . resolves ( {
137+ token : 'a-dummy-token' ,
138+ error : Error ( 'there was an error' )
139+ } ) ;
140+ await expect ( getToken ( appCheck , true ) ) . to . be . rejectedWith (
141+ 'there was an error'
142+ ) ;
143+ } ) ;
144+ } ) ;
145+ describe ( 'onTokenChanged()' , ( ) => {
146+ it ( 'Listeners work when using top-level parameters pattern' , async ( ) => {
147+ const appCheck = initializeAppCheck ( app , {
148+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
149+ isTokenAutoRefreshEnabled : true
150+ } ) ;
151+ const fakeRecaptchaToken = 'fake-recaptcha-token' ;
152+ const fakeRecaptchaAppCheckToken = {
153+ token : 'fake-recaptcha-app-check-token' ,
154+ expireTimeMillis : 123 ,
155+ issuedAtTimeMillis : 0
156+ } ;
157+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
158+ stub ( client , 'exchangeToken' ) . returns (
159+ Promise . resolve ( fakeRecaptchaAppCheckToken )
160+ ) ;
161+ stub ( storage , 'writeTokenToStorage' ) . returns ( Promise . resolve ( undefined ) ) ;
162+
163+ const listener1 = ( ) : void => {
164+ throw new Error ( ) ;
165+ } ;
166+ const listener2 = spy ( ) ;
167+
168+ const errorFn1 = spy ( ) ;
169+ const errorFn2 = spy ( ) ;
170+
171+ const unsubscribe1 = onTokenChanged ( appCheck , listener1 , errorFn1 ) ;
172+ const unsubscribe2 = onTokenChanged ( appCheck , listener2 , errorFn2 ) ;
173+
174+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 2 ) ;
175+
176+ await internalApi . getToken (
177+ appCheck . app ,
178+ getFakePlatformLoggingProvider ( )
179+ ) ;
180+
181+ expect ( listener2 ) . to . be . calledWith ( {
182+ token : fakeRecaptchaAppCheckToken . token
183+ } ) ;
184+ // onError should not be called on listener errors.
185+ expect ( errorFn1 ) . to . not . be . called ;
186+ expect ( errorFn2 ) . to . not . be . called ;
187+ unsubscribe1 ( ) ;
188+ unsubscribe2 ( ) ;
189+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 0 ) ;
190+ } ) ;
191+
192+ it ( 'Listeners work when using Observer pattern' , async ( ) => {
193+ const appCheck = initializeAppCheck ( app , {
194+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
195+ isTokenAutoRefreshEnabled : true
196+ } ) ;
197+ const fakePlatformLoggingProvider = getFakePlatformLoggingProvider ( ) ;
198+ const fakeRecaptchaToken = 'fake-recaptcha-token' ;
199+ const fakeRecaptchaAppCheckToken = {
200+ token : 'fake-recaptcha-app-check-token' ,
201+ expireTimeMillis : 123 ,
202+ issuedAtTimeMillis : 0
203+ } ;
204+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
205+ stub ( client , 'exchangeToken' ) . returns (
206+ Promise . resolve ( fakeRecaptchaAppCheckToken )
207+ ) ;
208+ stub ( storage , 'writeTokenToStorage' ) . returns ( Promise . resolve ( undefined ) ) ;
209+
210+ const listener1 = ( ) : void => {
211+ throw new Error ( ) ;
212+ } ;
213+ const listener2 = spy ( ) ;
214+
215+ const errorFn1 = spy ( ) ;
216+ const errorFn2 = spy ( ) ;
217+
218+ /**
219+ * Reverse the order of adding the failed and successful handler, for extra
220+ * testing.
221+ */
222+ const unsubscribe2 = onTokenChanged ( appCheck , {
223+ next : listener2 ,
224+ error : errorFn2
225+ } ) ;
226+ const unsubscribe1 = onTokenChanged ( appCheck , {
227+ next : listener1 ,
228+ error : errorFn1
229+ } ) ;
230+
231+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 2 ) ;
232+
233+ await internalApi . getToken ( appCheck . app , fakePlatformLoggingProvider ) ;
234+
235+ expect ( listener2 ) . to . be . calledWith ( {
236+ token : fakeRecaptchaAppCheckToken . token
237+ } ) ;
238+ // onError should not be called on listener errors.
239+ expect ( errorFn1 ) . to . not . be . called ;
240+ expect ( errorFn2 ) . to . not . be . called ;
241+ unsubscribe1 ( ) ;
242+ unsubscribe2 ( ) ;
243+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 0 ) ;
244+ } ) ;
245+
246+ it ( 'onError() catches token errors' , async ( ) => {
247+ stub ( logger . logger , 'error' ) ;
248+ const appCheck = initializeAppCheck ( app , {
249+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
250+ isTokenAutoRefreshEnabled : false
251+ } ) ;
252+ const fakePlatformLoggingProvider = getFakePlatformLoggingProvider ( ) ;
253+ const fakeRecaptchaToken = 'fake-recaptcha-token' ;
254+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
255+ stub ( client , 'exchangeToken' ) . rejects ( 'exchange error' ) ;
256+ stub ( storage , 'writeTokenToStorage' ) . returns ( Promise . resolve ( undefined ) ) ;
257+
258+ const listener1 = spy ( ) ;
259+
260+ const errorFn1 = spy ( ) ;
261+
262+ const unsubscribe1 = onTokenChanged ( appCheck , listener1 , errorFn1 ) ;
263+
264+ await internalApi . getToken ( app , fakePlatformLoggingProvider ) ;
265+
266+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 1 ) ;
267+
268+ expect ( errorFn1 ) . to . be . calledOnce ;
269+ expect ( errorFn1 . args [ 0 ] [ 0 ] . name ) . to . include ( 'exchange error' ) ;
270+
271+ unsubscribe1 ( ) ;
272+ expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 0 ) ;
273+ } ) ;
274+ } ) ;
95275} ) ;
0 commit comments