quodolores
Version:
Monorepo for the Firebase JavaScript SDK
237 lines (202 loc) • 7.62 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 { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
import { stubTimeouts, TimerMap } from '../../../test/helpers/timeout_stub';
import {
_EXPIRATION_TIME_MS,
_SOLVE_TIME_MS,
_WIDGET_ID_START,
MockReCaptcha,
MockWidget,
Widget
} from './recaptcha_mock';
use(sinonChai);
use(chaiAsPromised);
describe('platform_browser/recaptcha/recaptcha_mock', () => {
let container: HTMLElement;
let auth: TestAuth;
beforeEach(async () => {
container = document.createElement('div');
auth = await testAuth();
});
afterEach(() => {
sinon.restore();
});
describe('MockRecaptcha', () => {
let rc: MockReCaptcha;
let widget: Widget;
beforeEach(async () => {
rc = new MockReCaptcha(auth);
widget = {
getResponse: sinon.stub(),
delete: sinon.stub(),
execute: sinon.stub()
};
});
context('#reset', () => {
it('resets the default widget if no id provided', () => {
rc._widgets.set(_WIDGET_ID_START, widget);
rc.reset();
expect(widget.delete).to.have.been.called;
expect(rc._widgets).to.be.empty;
});
it('resets and removes the widgetId only if passed in', () => {
const widget2 = {
getResponse: sinon.stub(),
delete: sinon.stub(),
execute: sinon.stub()
};
rc._widgets.set(_WIDGET_ID_START, widget);
rc._widgets.set(_WIDGET_ID_START + 1, widget2);
rc.reset(_WIDGET_ID_START + 1);
expect(widget2.delete).to.have.been.called;
expect(widget.delete).not.to.have.been.called;
expect(rc._widgets.get(_WIDGET_ID_START)).to.eq(widget);
expect(rc._widgets.size).to.eq(1);
});
});
context('#render', () => {
// These tests all use invisible recaptcha to prevent the mock widget
// from setting timers
const params = { size: 'invisible' };
it('adds a mock widget object to the set and returns the id', () => {
const id = rc.render(container, params);
expect(id).to.eq(_WIDGET_ID_START);
expect(rc._widgets.get(_WIDGET_ID_START)).to.not.be.undefined;
});
it('sequentially creates new widgets', () => {
rc.render(container, params);
rc.render(container, params);
expect(rc._widgets.get(_WIDGET_ID_START)).to.not.be.undefined;
expect(rc._widgets.get(_WIDGET_ID_START + 1)).to.not.be.undefined;
});
});
context('#getResponse', () => {
it('returns the result from the widget if available', () => {
(widget.getResponse as sinon.SinonStub).returns('widget-result');
rc._widgets.set(_WIDGET_ID_START, widget);
expect(rc.getResponse()).to.eq('widget-result');
});
it('returns the empty string if the widget does not exist', () => {
expect(rc.getResponse()).to.eq('');
});
});
context('#execute', () => {
it('calls execute on the underlying widget', async () => {
rc._widgets.set(_WIDGET_ID_START, widget);
await rc.execute();
expect(widget.execute).to.have.been.called;
});
it('returns the empty string', async () => {
expect(await rc.execute()).to.eq('');
});
});
});
describe('MockWidget', () => {
context('#constructor', () => {
it('errors if a bad container is passed in', () => {
sinon.stub(document, 'getElementById').returns(null);
expect(() => new MockWidget('foo', 'app-name', {})).to.throw(
FirebaseError,
'Firebase: Error (auth/argument-error).'
);
});
it('attaches an event listener if invisible', () => {
sinon.spy(container, 'addEventListener');
void new MockWidget(container, 'app-name', { size: 'invisible' });
expect(container.addEventListener).to.have.been.called;
});
});
context('#execute', () => {
// Stub out a bunch of stuff on setTimer
let pendingTimers: TimerMap;
let callbacks: { [key: string]: sinon.SinonSpy };
let widget: MockWidget;
let timeoutStub: sinon.SinonStub;
beforeEach(() => {
callbacks = {
'callback': sinon.spy(),
'expired-callback': sinon.spy()
};
pendingTimers = stubTimeouts();
timeoutStub = (window.setTimeout as unknown) as sinon.SinonStub;
widget = new MockWidget(container, auth.name, callbacks);
});
it('keeps re-executing with new tokens if expiring', () => {
pendingTimers[_SOLVE_TIME_MS]();
pendingTimers[_EXPIRATION_TIME_MS]();
pendingTimers[_SOLVE_TIME_MS]();
pendingTimers[_EXPIRATION_TIME_MS]();
expect(callbacks['callback']).to.have.been.calledTwice;
expect(callbacks['callback'].getCall(0).args[0]).not.to.eq(
callbacks['callback'].getCall(1).args[0]
);
});
it('posts callback with a random alphanumeric code', () => {
pendingTimers[_SOLVE_TIME_MS]();
const arg: string = callbacks['callback'].getCall(0).args[0];
expect(arg).to.be.a('string');
expect(arg.length).to.equal(50);
});
it('expired callback does execute if just solve trips', () => {
pendingTimers[_SOLVE_TIME_MS]();
expect(callbacks['callback']).to.have.been.called;
expect(callbacks['expired-callback']).not.to.have.been.called;
});
it('expired callback executes if just expiration timer trips', () => {
pendingTimers[_SOLVE_TIME_MS]();
pendingTimers[_EXPIRATION_TIME_MS]();
expect(callbacks['callback']).to.have.been.called;
expect(callbacks['expired-callback']).to.have.been.called;
});
it('throws an error if the widget is deleted', () => {
widget.delete();
expect(() => widget.execute()).to.throw(Error);
});
it('returns immediately if timer is already in flight', () => {
expect(timeoutStub.getCalls().length).to.eq(1);
widget.execute();
expect(timeoutStub.getCalls().length).to.eq(1);
});
});
context('#delete', () => {
let widget: MockWidget;
beforeEach(() => {
widget = new MockWidget(container, auth.name, {});
});
it('throws if already deleted', () => {
widget.delete();
expect(() => widget.delete()).to.throw(Error);
});
it('clears any timeouts that are set', () => {
const spy = sinon.spy(window, 'clearTimeout');
widget.delete();
expect(spy).to.have.been.called;
});
it('removes the event listener from the container', () => {
const spy = sinon.spy(container, 'removeEventListener');
widget.delete();
expect(spy).to.have.been.called;
});
});
});
});