summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/services/gr-auth/gr-auth_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/services/gr-auth/gr-auth_test.ts')
-rw-r--r--polygerrit-ui/app/services/gr-auth/gr-auth_test.ts337
1 files changed, 337 insertions, 0 deletions
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_test.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_test.ts
new file mode 100644
index 0000000000..2b54bde822
--- /dev/null
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_test.ts
@@ -0,0 +1,337 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 '../../test/common-test-setup-karma';
+import {Auth} from './gr-auth_impl';
+import {getAppContext} from '../app-context';
+import {stubBaseUrl} from '../../test/test-utils';
+import {EventEmitterService} from '../gr-event-interface/gr-event-interface';
+import {SinonFakeTimers} from 'sinon';
+import {AuthRequestInit, DefaultAuthOptions} from './gr-auth';
+
+suite('gr-auth', () => {
+ let auth: Auth;
+ let eventEmitter: EventEmitterService;
+
+ setup(() => {
+ // TODO(poucet): Mock the eventEmitter completely instead of getting it
+ // from appContext.
+ eventEmitter = getAppContext().eventEmitter;
+ auth = new Auth(eventEmitter);
+ });
+
+ suite('Auth class methods', () => {
+ let fakeFetch: sinon.SinonStub;
+ setup(() => {
+ fakeFetch = sinon.stub(window, 'fetch');
+ });
+
+ test('auth-check returns 403', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ });
+
+ test('auth-check returns 204', async () => {
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const authed = await auth.authCheck();
+ assert.isTrue(authed);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ });
+
+ test('auth-check returns 502', async () => {
+ fakeFetch.returns(Promise.resolve({status: 502}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ });
+
+ test('auth-check failed', async () => {
+ fakeFetch.returns(Promise.reject(new Error('random error')));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.ERROR);
+ });
+ });
+
+ suite('cache and events behavior', () => {
+ let fakeFetch: sinon.SinonStub;
+ let clock: SinonFakeTimers;
+ setup(() => {
+ clock = sinon.useFakeTimers();
+ fakeFetch = sinon.stub(window, 'fetch');
+ });
+
+ test('cache auth-check result', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const authed2 = await auth.authCheck();
+ assert.isFalse(authed2);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ });
+
+ test('clearCache should refetch auth-check result', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ auth.clearCache();
+ const authed2 = await auth.authCheck();
+ assert.isTrue(authed2);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ });
+
+ test('cache expired on auth-check after certain time', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ clock.tick(1000 * 10000);
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const authed2 = await auth.authCheck();
+ assert.isTrue(authed2);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ });
+
+ test('no cache if auth-check failed', async () => {
+ fakeFetch.returns(Promise.reject(new Error('random error')));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.ERROR);
+ assert.equal(fakeFetch.callCount, 1);
+ await auth.authCheck();
+ assert.equal(fakeFetch.callCount, 2);
+ });
+
+ test('fire event when switch from authed to unauthed', async () => {
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const authed = await auth.authCheck();
+ assert.isTrue(authed);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ clock.tick(1000 * 10000);
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const emitStub = sinon.stub(eventEmitter, 'emit');
+ const authed2 = await auth.authCheck();
+ assert.isFalse(authed2);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ assert.isTrue(emitStub.called);
+ });
+
+ test('fire event when switch from authed to error', async () => {
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const authed = await auth.authCheck();
+ assert.isTrue(authed);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ clock.tick(1000 * 10000);
+ fakeFetch.returns(Promise.reject(new Error('random error')));
+ const emitStub = sinon.stub(eventEmitter, 'emit');
+ const authed2 = await auth.authCheck();
+ assert.isFalse(authed2);
+ assert.isTrue(emitStub.called);
+ assert.equal(auth.status, Auth.STATUS.ERROR);
+ });
+
+ test('no event from non-authed to other status', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ clock.tick(1000 * 10000);
+ fakeFetch.returns(Promise.resolve({status: 204}));
+ const emitStub = sinon.stub(eventEmitter, 'emit');
+ const authed2 = await auth.authCheck();
+ assert.isTrue(authed2);
+ assert.isFalse(emitStub.called);
+ assert.equal(auth.status, Auth.STATUS.AUTHED);
+ });
+
+ test('no event from non-authed to other status', async () => {
+ fakeFetch.returns(Promise.resolve({status: 403}));
+ const authed = await auth.authCheck();
+ assert.isFalse(authed);
+ assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+ clock.tick(1000 * 10000);
+ fakeFetch.returns(Promise.reject(new Error('random error')));
+ const emitStub = sinon.stub(eventEmitter, 'emit');
+ const authed2 = await auth.authCheck();
+ assert.isFalse(authed2);
+ assert.isFalse(emitStub.called);
+ assert.equal(auth.status, Auth.STATUS.ERROR);
+ });
+ });
+
+ suite('default (xsrf token header)', () => {
+ let fakeFetch: sinon.SinonStub;
+
+ setup(() => {
+ fakeFetch = sinon
+ .stub(window, 'fetch')
+ .returns(Promise.resolve({...new Response(), ok: true}));
+ });
+
+ test('GET', async () => {
+ await auth.fetch('/url', {bar: 'bar'} as AuthRequestInit);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.equal(url, '/url');
+ assert.equal(options.credentials, 'same-origin');
+ });
+
+ test('POST', async () => {
+ sinon.stub(auth, '_getCookie').withArgs('XSRF_TOKEN').returns('foobar');
+ await auth.fetch('/url', {method: 'POST'});
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.equal(url, '/url');
+ assert.equal(options.credentials, 'same-origin');
+ assert.equal(options.headers.get('X-Gerrit-Auth'), 'foobar');
+ });
+ });
+
+ suite('cors (access token)', () => {
+ let fakeFetch: sinon.SinonStub;
+
+ setup(() => {
+ fakeFetch = sinon
+ .stub(window, 'fetch')
+ .returns(Promise.resolve({...new Response(), ok: true}));
+ });
+
+ let getToken: sinon.SinonStub;
+
+ const makeToken = (opt_accessToken?: string) => {
+ return {
+ access_token: opt_accessToken || 'zbaz',
+ expires_at: new Date(Date.now() + 10e8).getTime(),
+ };
+ };
+
+ setup(() => {
+ getToken = sinon.stub();
+ getToken.returns(Promise.resolve(makeToken()));
+ const defaultOptions: DefaultAuthOptions = {
+ credentials: 'include',
+ };
+ auth.setup(getToken, defaultOptions);
+ });
+
+ test('base url support', async () => {
+ const baseUrl = 'http://foo';
+ stubBaseUrl(baseUrl);
+ await auth.fetch(baseUrl + '/url', {bar: 'bar'} as AuthRequestInit);
+ const [url] = fakeFetch.lastCall.args;
+ assert.equal(url, 'http://foo/a/url?access_token=zbaz');
+ });
+
+ test('fetch not signed in', async () => {
+ getToken.returns(Promise.resolve());
+ await auth.fetch('/url', {bar: 'bar'} as AuthRequestInit);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.equal(url, '/url');
+ assert.equal(options.bar, 'bar');
+ assert.equal(Object.keys(options.headers).length, 0);
+ });
+
+ test('fetch signed in', async () => {
+ await auth.fetch('/url', {bar: 'bar'} as AuthRequestInit);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.equal(url, '/a/url?access_token=zbaz');
+ assert.equal(options.bar, 'bar');
+ });
+
+ test('getToken calls are cached', async () => {
+ await Promise.all([auth.fetch('/url-one'), auth.fetch('/url-two')]);
+ assert.equal(getToken.callCount, 1);
+ });
+
+ test('getToken refreshes token', async () => {
+ const isTokenValidStub = sinon.stub(auth, '_isTokenValid');
+ isTokenValidStub
+ .onFirstCall()
+ .returns(true)
+ .onSecondCall()
+ .returns(false)
+ .onThirdCall()
+ .returns(true);
+ await auth.fetch('/url-one');
+ getToken.returns(Promise.resolve(makeToken('bzzbb')));
+ await auth.fetch('/url-two');
+
+ const [[firstUrl], [secondUrl]] = fakeFetch.args;
+ assert.equal(firstUrl, '/a/url-one?access_token=zbaz');
+ assert.equal(secondUrl, '/a/url-two?access_token=bzzbb');
+ });
+
+ test('signed in token error falls back to anonymous', async () => {
+ getToken.returns(Promise.resolve('rubbish'));
+ await auth.fetch('/url', {bar: 'bar'} as AuthRequestInit);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.equal(url, '/url');
+ assert.equal(options.bar, 'bar');
+ });
+
+ test('_isTokenValid', () => {
+ assert.isFalse(auth._isTokenValid(null));
+ assert.isFalse(auth._isTokenValid({}));
+ assert.isFalse(auth._isTokenValid({access_token: 'foo'}));
+ assert.isFalse(
+ auth._isTokenValid({
+ access_token: 'foo',
+ expires_at: `${Date.now() / 1000 - 1}`,
+ })
+ );
+ assert.isTrue(
+ auth._isTokenValid({
+ access_token: 'foo',
+ expires_at: `${Date.now() / 1000 + 1}`,
+ })
+ );
+ });
+
+ test('HTTP PUT with content type', async () => {
+ const originalOptions = {
+ method: 'PUT',
+ headers: new Headers({'Content-Type': 'mail/pigeon'}),
+ };
+ await auth.fetch('/url', originalOptions);
+ assert.isTrue(getToken.called);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.include(url, '$ct=mail%2Fpigeon');
+ assert.include(url, '$m=PUT');
+ assert.include(url, 'access_token=zbaz');
+ assert.equal(options.method, 'POST');
+ assert.equal(options.headers.get('Content-Type'), 'text/plain');
+ });
+
+ test('HTTP PUT without content type', async () => {
+ const originalOptions = {
+ method: 'PUT',
+ };
+ await auth.fetch('/url', originalOptions);
+ assert.isTrue(getToken.called);
+ const [url, options] = fakeFetch.lastCall.args;
+ assert.include(url, '$ct=text%2Fplain');
+ assert.include(url, '$m=PUT');
+ assert.include(url, 'access_token=zbaz');
+ assert.equal(options.method, 'POST');
+ assert.equal(options.headers.get('Content-Type'), 'text/plain');
+ });
+ });
+});