Skip to content

Commit dd7ccdd

Browse files
committed
working on tests
1 parent bb6f337 commit dd7ccdd

File tree

2 files changed

+369
-1
lines changed

2 files changed

+369
-1
lines changed

packages/app-check/src/internal-api.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import * as storage from './storage';
4040
import * as util from './util';
4141
import { getState, clearState, setState, getDebugState } from './state';
4242
import { AppCheckTokenListener } from './public-types';
43-
import { Deferred } from '@firebase/util';
43+
import { Deferred, FirebaseError } from '@firebase/util';
4444
import { ReCaptchaV3Provider } from './providers';
4545
import { AppCheckService } from './factory';
4646
import { ListenerType } from './types';
@@ -362,6 +362,25 @@ describe('internal api', () => {
362362
);
363363
expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token });
364364
});
365+
it('throttle', async () => {
366+
const appCheck = initializeAppCheck(app, {
367+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
368+
});
369+
stub(
370+
client,
371+
'exchangeToken'
372+
).returns(
373+
Promise.reject(
374+
new FirebaseError('test-error', 'test error msg', { httpStatus: 503 })
375+
)
376+
);
377+
378+
const token = await getToken(appCheck as AppCheckService);
379+
380+
expect(token.error?.message).to.equal(
381+
'sdfa'
382+
);
383+
});
365384
});
366385

367386
describe('addTokenListener', () => {
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import '../test/setup';
19+
import { expect } from 'chai';
20+
import { SinonStub, spy, stub, useFakeTimers } from 'sinon';
21+
import { deleteApp, FirebaseApp } from '@firebase/app';
22+
import {
23+
FAKE_SITE_KEY,
24+
getFullApp,
25+
getFakeCustomTokenProvider,
26+
removegreCAPTCHAScriptsOnPage,
27+
getFakeGreCAPTCHA
28+
} from '../test/util';
29+
import { initializeAppCheck } from './api';
30+
import {
31+
getToken,
32+
addTokenListener,
33+
removeTokenListener,
34+
formatDummyToken,
35+
defaultTokenErrorData
36+
} from './internal-api';
37+
import * as reCAPTCHA from './recaptcha';
38+
import * as client from './client';
39+
import * as storage from './storage';
40+
import * as util from './util';
41+
import { getState, clearState, setState, getDebugState } from './state';
42+
import { AppCheckTokenListener } from './public-types';
43+
import { Deferred, FirebaseError } from '@firebase/util';
44+
import { ReCaptchaV3Provider } from './providers';
45+
import { AppCheckService } from './factory';
46+
import { ListenerType } from './types';
47+
48+
const fakeRecaptchaToken = 'fake-recaptcha-token';
49+
const fakeRecaptchaAppCheckToken = {
50+
token: 'fake-recaptcha-app-check-token',
51+
expireTimeMillis: Date.now() + 60000,
52+
issuedAtTimeMillis: 0
53+
};
54+
55+
const fakeCachedAppCheckToken = {
56+
token: 'fake-cached-app-check-token',
57+
expireTimeMillis: Date.now() + 60000,
58+
issuedAtTimeMillis: 0
59+
};
60+
61+
describe('internal api', () => {
62+
let app: FirebaseApp;
63+
let storageReadStub: SinonStub;
64+
let storageWriteStub: SinonStub;
65+
66+
beforeEach(() => {
67+
app = getFullApp();
68+
storageReadStub = stub(storage, 'readTokenFromStorage').resolves(undefined);
69+
storageWriteStub = stub(storage, 'writeTokenToStorage');
70+
stub(util, 'getRecaptcha').returns(getFakeGreCAPTCHA());
71+
});
72+
73+
afterEach(() => {
74+
clearState();
75+
removegreCAPTCHAScriptsOnPage();
76+
return deleteApp(app);
77+
});
78+
// TODO: test error conditions
79+
describe('getToken()', () => {
80+
it('uses reCAPTCHA token to exchange for AppCheck token', async () => {
81+
const appCheck = initializeAppCheck(app, {
82+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
83+
});
84+
const exchangeTokenStub: SinonStub = stub(
85+
client,
86+
'exchangeToken'
87+
).returns(
88+
Promise.reject(
89+
new FirebaseError('test-error', 'test error msg', { httpStatus: 503 })
90+
)
91+
);
92+
93+
const token = await getToken(appCheck as AppCheckService);
94+
95+
expect(exchangeTokenStub.args[0][0].body['recaptcha_token']).to.equal(
96+
fakeRecaptchaToken
97+
);
98+
expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token });
99+
});
100+
101+
it('resolves with a dummy token and an error if failed to get a token', async () => {
102+
const errorStub = stub(console, 'error');
103+
const appCheck = initializeAppCheck(app, {
104+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
105+
});
106+
107+
const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').returns(
108+
Promise.resolve(fakeRecaptchaToken)
109+
);
110+
111+
const error = new Error('oops, something went wrong');
112+
stub(client, 'exchangeToken').returns(Promise.reject(error));
113+
114+
const token = await getToken(appCheck as AppCheckService);
115+
116+
expect(reCAPTCHASpy).to.be.called;
117+
expect(token).to.deep.equal({
118+
token: formatDummyToken(defaultTokenErrorData),
119+
error
120+
});
121+
expect(errorStub.args[0][1].message).to.include(
122+
'oops, something went wrong'
123+
);
124+
errorStub.restore();
125+
});
126+
127+
it('notifies listeners using cached token', async () => {
128+
storageReadStub.resolves(fakeCachedAppCheckToken);
129+
const appCheck = initializeAppCheck(app, {
130+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY),
131+
isTokenAutoRefreshEnabled: false
132+
});
133+
134+
const clock = useFakeTimers();
135+
136+
const listener1 = spy();
137+
const listener2 = spy();
138+
addTokenListener(
139+
appCheck as AppCheckService,
140+
ListenerType.INTERNAL,
141+
listener1
142+
);
143+
addTokenListener(
144+
appCheck as AppCheckService,
145+
ListenerType.INTERNAL,
146+
listener2
147+
);
148+
149+
await getToken(appCheck as AppCheckService);
150+
151+
clock.tick(1);
152+
153+
expect(listener1).to.be.calledWith({
154+
token: fakeCachedAppCheckToken.token
155+
});
156+
expect(listener2).to.be.calledWith({
157+
token: fakeCachedAppCheckToken.token
158+
});
159+
160+
clock.restore();
161+
});
162+
163+
it('notifies listeners using new token', async () => {
164+
const appCheck = initializeAppCheck(app, {
165+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY),
166+
isTokenAutoRefreshEnabled: true
167+
});
168+
169+
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
170+
stub(client, 'exchangeToken').returns(
171+
Promise.resolve(fakeRecaptchaAppCheckToken)
172+
);
173+
174+
const listener1 = spy();
175+
const listener2 = spy();
176+
addTokenListener(
177+
appCheck as AppCheckService,
178+
ListenerType.INTERNAL,
179+
listener1
180+
);
181+
addTokenListener(
182+
appCheck as AppCheckService,
183+
ListenerType.INTERNAL,
184+
listener2
185+
);
186+
187+
await getToken(appCheck as AppCheckService);
188+
189+
expect(listener1).to.be.calledWith({
190+
token: fakeRecaptchaAppCheckToken.token
191+
});
192+
expect(listener2).to.be.calledWith({
193+
token: fakeRecaptchaAppCheckToken.token
194+
});
195+
});
196+
197+
it('calls 3P error handler if there is an error getting a token', async () => {
198+
stub(console, 'error');
199+
const appCheck = initializeAppCheck(app, {
200+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY),
201+
isTokenAutoRefreshEnabled: true
202+
});
203+
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
204+
stub(client, 'exchangeToken').rejects('exchange error');
205+
const listener1 = spy();
206+
const errorFn1 = spy();
207+
208+
addTokenListener(
209+
appCheck as AppCheckService,
210+
ListenerType.EXTERNAL,
211+
listener1,
212+
errorFn1
213+
);
214+
215+
await getToken(appCheck as AppCheckService);
216+
217+
expect(errorFn1).to.be.calledOnce;
218+
expect(errorFn1.args[0][0].name).to.include('exchange error');
219+
});
220+
221+
it('ignores listeners that throw', async () => {
222+
stub(console, 'error');
223+
const appCheck = initializeAppCheck(app, {
224+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY),
225+
isTokenAutoRefreshEnabled: true
226+
});
227+
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
228+
stub(client, 'exchangeToken').returns(
229+
Promise.resolve(fakeRecaptchaAppCheckToken)
230+
);
231+
const listener1 = stub().throws(new Error());
232+
const listener2 = spy();
233+
234+
addTokenListener(
235+
appCheck as AppCheckService,
236+
ListenerType.INTERNAL,
237+
listener1
238+
);
239+
addTokenListener(
240+
appCheck as AppCheckService,
241+
ListenerType.INTERNAL,
242+
listener2
243+
);
244+
245+
await getToken(appCheck as AppCheckService);
246+
247+
expect(listener1).to.be.calledWith({
248+
token: fakeRecaptchaAppCheckToken.token
249+
});
250+
expect(listener2).to.be.calledWith({
251+
token: fakeRecaptchaAppCheckToken.token
252+
});
253+
});
254+
255+
it('loads persisted token to memory and returns it', async () => {
256+
const clock = useFakeTimers();
257+
258+
storageReadStub.resolves(fakeCachedAppCheckToken);
259+
const appCheck = initializeAppCheck(app, {
260+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
261+
});
262+
263+
const clientStub = stub(client, 'exchangeToken');
264+
265+
expect(getState(app).token).to.equal(undefined);
266+
expect(await getToken(appCheck as AppCheckService)).to.deep.equal({
267+
token: fakeCachedAppCheckToken.token
268+
});
269+
expect(getState(app).token).to.equal(fakeCachedAppCheckToken);
270+
expect(clientStub).has.not.been.called;
271+
272+
clock.restore();
273+
});
274+
275+
it('persists token to storage', async () => {
276+
const appCheck = initializeAppCheck(app, {
277+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
278+
});
279+
280+
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
281+
stub(client, 'exchangeToken').returns(
282+
Promise.resolve(fakeRecaptchaAppCheckToken)
283+
);
284+
storageWriteStub.resetHistory();
285+
const result = await getToken(appCheck as AppCheckService);
286+
expect(result).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token });
287+
expect(storageWriteStub).has.been.calledWith(
288+
app,
289+
fakeRecaptchaAppCheckToken
290+
);
291+
});
292+
293+
it('returns the valid token in memory without making network request', async () => {
294+
const clock = useFakeTimers();
295+
const appCheck = initializeAppCheck(app, {
296+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
297+
});
298+
setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken });
299+
300+
const clientStub = stub(client, 'exchangeToken');
301+
expect(await getToken(appCheck as AppCheckService)).to.deep.equal({
302+
token: fakeRecaptchaAppCheckToken.token
303+
});
304+
expect(clientStub).to.not.have.been.called;
305+
306+
clock.restore();
307+
});
308+
309+
it('force to get new token when forceRefresh is true', async () => {
310+
const appCheck = initializeAppCheck(app, {
311+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
312+
});
313+
setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken });
314+
315+
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
316+
stub(client, 'exchangeToken').returns(
317+
Promise.resolve({
318+
token: 'new-recaptcha-app-check-token',
319+
expireTimeMillis: Date.now() + 60000,
320+
issuedAtTimeMillis: 0
321+
})
322+
);
323+
324+
expect(await getToken(appCheck as AppCheckService, true)).to.deep.equal({
325+
token: 'new-recaptcha-app-check-token'
326+
});
327+
});
328+
329+
it('exchanges debug token if in debug mode and there is no cached token', async () => {
330+
const exchangeTokenStub: SinonStub = stub(
331+
client,
332+
'exchangeToken'
333+
).returns(Promise.resolve(fakeRecaptchaAppCheckToken));
334+
const debugState = getDebugState();
335+
debugState.enabled = true;
336+
debugState.token = new Deferred();
337+
debugState.token.resolve('my-debug-token');
338+
const appCheck = initializeAppCheck(app, {
339+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
340+
});
341+
342+
const token = await getToken(appCheck as AppCheckService);
343+
expect(exchangeTokenStub.args[0][0].body['debug_token']).to.equal(
344+
'my-debug-token'
345+
);
346+
expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token });
347+
});
348+
});
349+
});

0 commit comments

Comments
 (0)