quodolores
Version:
Monorepo for the Firebase JavaScript SDK
198 lines (171 loc) • 6.73 kB
text/typescript
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { FirebaseError } from '@firebase/util';
import { mockEndpoint } from '../../../test/helpers/api/helper';
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
import * as fetch from '../../../test/helpers/mock_fetch';
import { Endpoint } from '../../api';
import { _window } from '../auth_window';
import { Parameters, Recaptcha } from './recaptcha';
import { ReCaptchaLoader } from './recaptcha_loader';
import { MockReCaptcha } from './recaptcha_mock';
import { RecaptchaVerifier } from './recaptcha_verifier';
use(chaiAsPromised);
use(sinonChai);
describe('platform_browser/recaptcha/recaptcha_verifier', () => {
let auth: TestAuth;
let container: HTMLElement;
let verifier: RecaptchaVerifier;
let parameters: Parameters;
let recaptchaLoader: ReCaptchaLoader;
beforeEach(async () => {
fetch.setUp();
auth = await testAuth();
auth.languageCode = 'fr';
container = document.createElement('div');
parameters = {};
verifier = new RecaptchaVerifier(container, parameters, auth);
// The verifier will have set the parameters.callback field to be the wrapped callback
mockEndpoint(Endpoint.GET_RECAPTCHA_PARAM, {
recaptchaSiteKey: 'recaptcha-key'
});
recaptchaLoader = verifier._recaptchaLoader;
});
afterEach(() => {
sinon.restore();
fetch.tearDown();
});
context('#render', () => {
it('caches the promise if not completed and returns if called multiple times', () => {
// This will force the loader to never return so the render promise never completes
sinon.stub(recaptchaLoader, 'load').returns(new Promise(() => {}));
const renderPromise = verifier.render();
expect(verifier.render()).to.eq(renderPromise);
});
it('appends an empty div to the container element', async () => {
expect(container.childElementCount).to.eq(0);
await verifier.render();
expect(container.childElementCount).to.eq(1);
});
it('sets the site key on the parameters object', async () => {
await verifier.render();
expect(parameters.sitekey).to.eq('recaptcha-key');
});
it('sets loads the recaptcha per the app language code', async () => {
sinon.spy(recaptchaLoader, 'load');
await verifier.render();
expect(recaptchaLoader.load).to.have.been.calledWith(auth, 'fr');
});
it('calls render on the underlying recaptcha widget', async () => {
const widget = new MockReCaptcha(auth);
sinon.spy(widget, 'render');
sinon.stub(recaptchaLoader, 'load').returns(Promise.resolve(widget));
await verifier.render();
expect(widget.render).to.have.been.calledWith(
container.children[0],
parameters
);
});
it('in case of error, resets render promise', async () => {
sinon.stub(recaptchaLoader, 'load').returns(Promise.reject('nope'));
const promise = verifier.render();
await expect(promise).to.be.rejectedWith('nope');
expect(verifier.render()).not.to.eq(promise);
});
});
context('#verify', () => {
let recaptcha: Recaptcha;
beforeEach(() => {
recaptcha = new MockReCaptcha(auth);
sinon.stub(recaptchaLoader, 'load').returns(Promise.resolve(recaptcha));
});
it('returns immediately if response is available', async () => {
sinon.stub(recaptcha, 'getResponse').returns('recaptcha-response');
expect(await verifier.verify()).to.eq('recaptcha-response');
});
it('resolves with the token in the callback', async () => {
sinon.stub(recaptcha, 'getResponse').returns('');
const promise = verifier.verify();
expect(typeof (await promise)).to.eq('string');
});
it('calls existing callback if provided', async () => {
let token = '';
parameters = {
callback: (t: string): void => {
token = t;
}
};
verifier = new RecaptchaVerifier(container, parameters, auth);
const expected = await verifier.verify();
expect(token).to.eq(expected);
});
it('calls existing global function if on the window', async () => {
let token = '';
_window().callbackOnWindowObject = (t: unknown): void => {
token = t as string;
};
parameters = {
callback: 'callbackOnWindowObject'
};
verifier = new RecaptchaVerifier(container, parameters, auth);
const expected = await verifier.verify();
expect(token).to.eq(expected);
delete _window().callbackOnWindowObject;
});
});
context('#reset', () => {
it('calls reset on the underlying widget', async () => {
const recaptcha = new MockReCaptcha(auth);
sinon.stub(recaptchaLoader, 'load').returns(Promise.resolve(recaptcha));
sinon.spy(recaptcha, 'reset');
await verifier.render();
verifier._reset();
expect(recaptcha.reset).to.have.been.called;
});
});
context('#clear', () => {
it('removes the child node from the container', async () => {
await verifier.render();
expect(container.children.length).to.eq(1);
verifier.clear();
expect(container.children.length).to.eq(0);
});
it('causes other methods of the verifier to throw if called subsequently', async () => {
verifier.clear();
expect(() => verifier.clear()).to.throw(
FirebaseError,
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
);
expect(() => verifier._reset()).to.throw(
FirebaseError,
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
);
await expect(verifier.render()).to.be.rejectedWith(
FirebaseError,
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
);
await expect(verifier.verify()).to.be.rejectedWith(
FirebaseError,
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
);
});
});
});