Revert "Refactor gr-rest-api-interface - extract common methods to helper"
This reverts commit e2d84b21ad.
Reason for revert: document.createElement('gr-rest-api-interface').send(...) doesn't work in Polymer 2, because the ready() method of gr-rest-api-interface calls later.
Change-Id: I42234c2e2e6801b144e4d1db33e9d7929ace219e
This commit is contained in:
@@ -29,7 +29,6 @@ limitations under the License.
|
||||
|
||||
<dom-module id="gr-rest-api-interface">
|
||||
<!-- NB: Order is important, because of namespaced classes. -->
|
||||
<script src="gr-rest-apis/gr-rest-api-helper.js"></script>
|
||||
<script src="gr-auth.js"></script>
|
||||
<script src="gr-reviewer-updates-parser.js"></script>
|
||||
<script src="gr-rest-api-interface.js"></script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,8 +63,116 @@ limitations under the License.
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('fetchJSON()', () => {
|
||||
test('Sets header to accept application/json', () => {
|
||||
const authFetchStub = sandbox.stub(element._auth, 'fetch')
|
||||
.returns(Promise.resolve());
|
||||
element._fetchJSON({url: '/dummy/url'});
|
||||
assert.isTrue(authFetchStub.called);
|
||||
assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
|
||||
'application/json');
|
||||
});
|
||||
|
||||
test('Use header option accept when provided', () => {
|
||||
const authFetchStub = sandbox.stub(element._auth, 'fetch')
|
||||
.returns(Promise.resolve());
|
||||
const headers = new Headers();
|
||||
headers.append('Accept', '*/*');
|
||||
const fetchOptions = {headers};
|
||||
element._fetchJSON({url: '/dummy/url', fetchOptions});
|
||||
assert.isTrue(authFetchStub.called);
|
||||
assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
|
||||
'*/*');
|
||||
});
|
||||
});
|
||||
|
||||
test('JSON prefix is properly removed', done => {
|
||||
element._fetchJSON({url: '/dummy/url'}).then(obj => {
|
||||
assert.deepEqual(obj, {hello: 'bonjour'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('cached results', done => {
|
||||
let n = 0;
|
||||
sandbox.stub(element, '_fetchJSON', () => {
|
||||
return Promise.resolve(++n);
|
||||
});
|
||||
const promises = [];
|
||||
promises.push(element._fetchSharedCacheURL('/foo'));
|
||||
promises.push(element._fetchSharedCacheURL('/foo'));
|
||||
promises.push(element._fetchSharedCacheURL('/foo'));
|
||||
|
||||
Promise.all(promises).then(results => {
|
||||
assert.deepEqual(results, [1, 1, 1]);
|
||||
element._fetchSharedCacheURL('/foo').then(foo => {
|
||||
assert.equal(foo, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('cached promise', done => {
|
||||
const promise = Promise.reject(new Error('foo'));
|
||||
element._cache.set('/foo', promise);
|
||||
element._fetchSharedCacheURL({url: '/foo'}).catch(p => {
|
||||
assert.equal(p.message, 'foo');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('cache invalidation', () => {
|
||||
element._cache.set('/foo/bar', 1);
|
||||
element._cache.set('/bar', 2);
|
||||
element._sharedFetchPromises['/foo/bar'] = 3;
|
||||
element._sharedFetchPromises['/bar'] = 4;
|
||||
element._invalidateSharedFetchPromisesPrefix('/foo/');
|
||||
assert.isFalse(element._cache.has('/foo/bar'));
|
||||
assert.isTrue(element._cache.has('/bar'));
|
||||
assert.isUndefined(element._sharedFetchPromises['/foo/bar']);
|
||||
assert.strictEqual(4, element._sharedFetchPromises['/bar']);
|
||||
});
|
||||
|
||||
test('params are properly encoded', () => {
|
||||
let url = element._urlWithParams('/path/', {
|
||||
sp: 'hola',
|
||||
gr: 'guten tag',
|
||||
noval: null,
|
||||
});
|
||||
assert.equal(url,
|
||||
window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
|
||||
|
||||
url = element._urlWithParams('/path/', {
|
||||
sp: 'hola',
|
||||
en: ['hey', 'hi'],
|
||||
});
|
||||
assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
|
||||
|
||||
// Order must be maintained with array params.
|
||||
url = element._urlWithParams('/path/', {
|
||||
l: ['c', 'b', 'a'],
|
||||
});
|
||||
assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
|
||||
});
|
||||
|
||||
test('request callbacks can be canceled', done => {
|
||||
let cancelCalled = false;
|
||||
window.fetch.returns(Promise.resolve({
|
||||
body: {
|
||||
cancel() { cancelCalled = true; },
|
||||
},
|
||||
}));
|
||||
const cancelCondition = () => { return true; };
|
||||
element._fetchJSON({url: '/dummy/url', cancelCondition}).then(
|
||||
obj => {
|
||||
assert.isUndefined(obj);
|
||||
assert.isTrue(cancelCalled);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('parent diff comments are properly grouped', done => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchJSON', () => {
|
||||
sandbox.stub(element, '_fetchJSON', () => {
|
||||
return Promise.resolve({
|
||||
'/COMMIT_MSG': [],
|
||||
'sieve.go': [
|
||||
@@ -207,7 +315,7 @@ limitations under the License.
|
||||
test('differing patch diff comments are properly grouped', done => {
|
||||
sandbox.stub(element, 'getFromProjectLookup')
|
||||
.returns(Promise.resolve('test'));
|
||||
sandbox.stub(element._restApiHelper, 'fetchJSON', request => {
|
||||
sandbox.stub(element, '_fetchJSON', request => {
|
||||
const url = request.url;
|
||||
if (url === '/changes/test~42/revisions/1') {
|
||||
return Promise.resolve({
|
||||
@@ -324,7 +432,7 @@ limitations under the License.
|
||||
suite('rebase action', () => {
|
||||
let resolve_fetchJSON;
|
||||
setup(() => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchJSON').returns(
|
||||
sandbox.stub(element, '_fetchJSON').returns(
|
||||
new Promise(resolve => {
|
||||
resolve_fetchJSON = resolve;
|
||||
}));
|
||||
@@ -359,7 +467,7 @@ limitations under the License.
|
||||
element.addEventListener('server-error', resolve);
|
||||
});
|
||||
|
||||
element._restApiHelper.fetchJSON({}).then(response => {
|
||||
element._fetchJSON({}).then(response => {
|
||||
assert.isUndefined(response);
|
||||
assert.isTrue(getResponseObjectStub.notCalled);
|
||||
serverErrorEventPromise.then(() => done());
|
||||
@@ -375,12 +483,12 @@ limitations under the License.
|
||||
Promise.reject(new Error('Failed to fetch')));
|
||||
window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse));
|
||||
// Emulate logged in.
|
||||
element._restApiHelper._cache.set('/accounts/self/detail', {});
|
||||
element._cache.set('/accounts/self/detail', {});
|
||||
const serverErrorStub = sandbox.stub();
|
||||
element.addEventListener('server-error', serverErrorStub);
|
||||
const authErrorStub = sandbox.stub();
|
||||
element.addEventListener('auth-error', authErrorStub);
|
||||
element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
|
||||
element._fetchJSON({url: '/bar'}).finally(r => {
|
||||
flush(() => {
|
||||
assert.isTrue(authErrorStub.called);
|
||||
assert.isFalse(serverErrorStub.called);
|
||||
@@ -399,7 +507,7 @@ limitations under the License.
|
||||
element.addEventListener('server-error', serverErrorStub);
|
||||
const authErrorStub = sandbox.stub();
|
||||
element.addEventListener('auth-error', authErrorStub);
|
||||
element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
|
||||
element._fetchJSON({url: '/bar'}).finally(r => {
|
||||
flush(() => {
|
||||
assert.isTrue(authErrorStub.called);
|
||||
assert.isFalse(serverErrorStub.called);
|
||||
@@ -450,8 +558,7 @@ limitations under the License.
|
||||
test('checkCredentials promise rejection', () => {
|
||||
window.fetch.restore();
|
||||
element._cache.set('/accounts/self/detail', true);
|
||||
const checkCredentialsSpy =
|
||||
sandbox.spy(element._restApiHelper, 'checkCredentials');
|
||||
sandbox.spy(element, 'checkCredentials');
|
||||
sandbox.stub(window, 'fetch', url => {
|
||||
return Promise.reject(new Error('Failed to fetch'));
|
||||
});
|
||||
@@ -463,7 +570,7 @@ limitations under the License.
|
||||
// The second fetch call also fails, which leads to a second
|
||||
// invocation of checkCredentials, which should immediately
|
||||
// return instead of making further fetch calls.
|
||||
assert.isTrue(checkCredentialsSpy .calledTwice);
|
||||
assert.isTrue(element.checkCredentials.calledTwice);
|
||||
assert.isTrue(window.fetch.calledTwice);
|
||||
});
|
||||
});
|
||||
@@ -478,7 +585,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('legacy n,z key in change url is replaced', () => {
|
||||
const stub = sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||
const stub = sandbox.stub(element, '_fetchJSON')
|
||||
.returns(Promise.resolve([]));
|
||||
element.getChanges(1, null, 'n,z');
|
||||
assert.equal(stub.lastCall.args[0].params.S, 0);
|
||||
@@ -486,38 +593,38 @@ limitations under the License.
|
||||
|
||||
test('saveDiffPreferences invalidates cache line', () => {
|
||||
const cacheKey = '/accounts/self/preferences.diff';
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||
sandbox.stub(element, '_send');
|
||||
element._cache.set(cacheKey, {tab_size: 4});
|
||||
element.saveDiffPreferences({tab_size: 8});
|
||||
assert.isTrue(sendStub.called);
|
||||
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||
assert.isTrue(element._send.called);
|
||||
assert.isFalse(element._cache.has(cacheKey));
|
||||
});
|
||||
|
||||
test('getAccount when resp is null does not add anything to the cache',
|
||||
done => {
|
||||
const cacheKey = '/accounts/self/detail';
|
||||
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
||||
() => Promise.resolve());
|
||||
|
||||
element.getAccount().then(() => {
|
||||
assert.isTrue(stub.called);
|
||||
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
||||
assert.isFalse(element._cache.has(cacheKey));
|
||||
done();
|
||||
});
|
||||
|
||||
element._restApiHelper._cache.set(cacheKey, 'fake cache');
|
||||
element._cache.set(cacheKey, 'fake cache');
|
||||
stub.lastCall.args[0].errFn();
|
||||
});
|
||||
|
||||
test('getAccount does not add to the cache when resp.status is 403',
|
||||
done => {
|
||||
const cacheKey = '/accounts/self/detail';
|
||||
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
||||
() => Promise.resolve());
|
||||
|
||||
element.getAccount().then(() => {
|
||||
assert.isTrue(stub.called);
|
||||
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
||||
assert.isFalse(element._cache.has(cacheKey));
|
||||
done();
|
||||
});
|
||||
element._cache.set(cacheKey, 'fake cache');
|
||||
@@ -526,15 +633,15 @@ limitations under the License.
|
||||
|
||||
test('getAccount when resp is successful', done => {
|
||||
const cacheKey = '/accounts/self/detail';
|
||||
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
||||
() => Promise.resolve());
|
||||
|
||||
element.getAccount().then(response => {
|
||||
assert.isTrue(stub.called);
|
||||
assert.equal(element._restApiHelper._cache.get(cacheKey), 'fake cache');
|
||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
||||
assert.equal(element._cache.get(cacheKey), 'fake cache');
|
||||
done();
|
||||
});
|
||||
element._restApiHelper._cache.set(cacheKey, 'fake cache');
|
||||
element._cache.set(cacheKey, 'fake cache');
|
||||
|
||||
stub.lastCall.args[0].errFn({});
|
||||
});
|
||||
@@ -546,7 +653,7 @@ limitations under the License.
|
||||
sandbox.stub(element, '_isNarrowScreen', () => {
|
||||
return smallScreen;
|
||||
});
|
||||
sandbox.stub(element._restApiHelper, 'fetchCacheURL', () => {
|
||||
sandbox.stub(element, '_fetchSharedCacheURL', () => {
|
||||
return Promise.resolve(testJSON);
|
||||
});
|
||||
};
|
||||
@@ -611,10 +718,10 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('savPreferences normalizes download scheme', () => {
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||
sandbox.stub(element, '_send');
|
||||
element.savePreferences({download_scheme: 'HTTP'});
|
||||
assert.isTrue(sendStub.called);
|
||||
assert.equal(sendStub.lastCall.args[0].body.download_scheme, 'http');
|
||||
assert.isTrue(element._send.called);
|
||||
assert.equal(element._send.lastCall.args[0].body.download_scheme, 'http');
|
||||
});
|
||||
|
||||
test('getDiffPreferences returns correct defaults', done => {
|
||||
@@ -640,10 +747,10 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('saveDiffPreferences set show_tabs to false', () => {
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||
sandbox.stub(element, '_send');
|
||||
element.saveDiffPreferences({show_tabs: false});
|
||||
assert.isTrue(sendStub.called);
|
||||
assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
|
||||
assert.isTrue(element._send.called);
|
||||
assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
|
||||
});
|
||||
|
||||
test('getEditPreferences returns correct defaults', done => {
|
||||
@@ -673,35 +780,33 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('saveEditPreferences set show_tabs to false', () => {
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||
sandbox.stub(element, '_send');
|
||||
element.saveEditPreferences({show_tabs: false});
|
||||
assert.isTrue(sendStub.called);
|
||||
assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
|
||||
assert.isTrue(element._send.called);
|
||||
assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
|
||||
});
|
||||
|
||||
test('confirmEmail', () => {
|
||||
const sendStub = sandbox.spy(element._restApiHelper, 'send');
|
||||
sandbox.spy(element, '_send');
|
||||
element.confirmEmail('foo');
|
||||
assert.isTrue(sendStub.calledOnce);
|
||||
assert.equal(sendStub.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(sendStub.lastCall.args[0].url,
|
||||
assert.isTrue(element._send.calledOnce);
|
||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(element._send.lastCall.args[0].url,
|
||||
'/config/server/email.confirm');
|
||||
assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
|
||||
assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
|
||||
});
|
||||
|
||||
test('setAccountStatus', () => {
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||
.returns(Promise.resolve('OOO'));
|
||||
sandbox.stub(element, '_send').returns(Promise.resolve('OOO'));
|
||||
element._cache.set('/accounts/self/detail', {});
|
||||
return element.setAccountStatus('OOO').then(() => {
|
||||
assert.isTrue(sendStub.calledOnce);
|
||||
assert.equal(sendStub.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(sendStub.lastCall.args[0].url,
|
||||
assert.isTrue(element._send.calledOnce);
|
||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(element._send.lastCall.args[0].url,
|
||||
'/accounts/self/status');
|
||||
assert.deepEqual(sendStub.lastCall.args[0].body,
|
||||
assert.deepEqual(element._send.lastCall.args[0].body,
|
||||
{status: 'OOO'});
|
||||
assert.deepEqual(element._restApiHelper
|
||||
._cache.get('/accounts/self/detail'),
|
||||
assert.deepEqual(element._cache.get('/accounts/self/detail'),
|
||||
{status: 'OOO'});
|
||||
});
|
||||
});
|
||||
@@ -791,20 +896,18 @@ limitations under the License.
|
||||
const change_num = '1';
|
||||
const file_name = 'index.php';
|
||||
const file_contents = '<?php';
|
||||
sandbox.stub(element._restApiHelper, 'send').returns(
|
||||
sandbox.stub(element, '_send').returns(
|
||||
Promise.resolve([change_num, file_name, file_contents]));
|
||||
sandbox.stub(element, 'getResponseObject')
|
||||
.returns(Promise.resolve([change_num, file_name, file_contents]));
|
||||
element._cache.set('/changes/' + change_num + '/edit/' + file_name, {});
|
||||
return element.saveChangeEdit(change_num, file_name, file_contents)
|
||||
.then(() => {
|
||||
assert.isTrue(element._restApiHelper.send.calledOnce);
|
||||
assert.equal(element._restApiHelper.send.lastCall.args[0].method,
|
||||
'PUT');
|
||||
assert.equal(element._restApiHelper.send.lastCall.args[0].url,
|
||||
assert.isTrue(element._send.calledOnce);
|
||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(element._send.lastCall.args[0].url,
|
||||
'/changes/test~1/edit/' + file_name);
|
||||
assert.equal(element._restApiHelper.send.lastCall.args[0].body,
|
||||
file_contents);
|
||||
assert.equal(element._send.lastCall.args[0].body, file_contents);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -812,18 +915,17 @@ limitations under the License.
|
||||
element._projectLookup = {1: 'test'};
|
||||
const change_num = '1';
|
||||
const message = 'this is a commit message';
|
||||
sandbox.stub(element._restApiHelper, 'send').returns(
|
||||
sandbox.stub(element, '_send').returns(
|
||||
Promise.resolve([change_num, message]));
|
||||
sandbox.stub(element, 'getResponseObject')
|
||||
.returns(Promise.resolve([change_num, message]));
|
||||
element._cache.set('/changes/' + change_num + '/message', {});
|
||||
return element.putChangeCommitMessage(change_num, message).then(() => {
|
||||
assert.isTrue(element._restApiHelper.send.calledOnce);
|
||||
assert.equal(element._restApiHelper.send.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(element._restApiHelper.send.lastCall.args[0].url,
|
||||
assert.isTrue(element._send.calledOnce);
|
||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
||||
assert.equal(element._send.lastCall.args[0].url,
|
||||
'/changes/test~1/message');
|
||||
assert.deepEqual(element._restApiHelper.send.lastCall.args[0].body,
|
||||
{message});
|
||||
assert.deepEqual(element._send.lastCall.args[0].body, {message});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -879,7 +981,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('createRepo encodes name', () => {
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||
const sendStub = sandbox.stub(element, '_send')
|
||||
.returns(Promise.resolve());
|
||||
return element.createRepo({name: 'x/y'}).then(() => {
|
||||
assert.isTrue(sendStub.calledOnce);
|
||||
@@ -925,65 +1027,64 @@ limitations under the License.
|
||||
|
||||
suite('getRepos', () => {
|
||||
const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
|
||||
let fetchCacheURLStub;
|
||||
|
||||
setup(() => {
|
||||
fetchCacheURLStub =
|
||||
sandbox.stub(element._restApiHelper, 'fetchCacheURL');
|
||||
sandbox.stub(element, '_fetchSharedCacheURL');
|
||||
});
|
||||
|
||||
test('normal use', () => {
|
||||
element.getRepos('test', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=test');
|
||||
|
||||
element.getRepos(null, 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
||||
|
||||
element.getRepos('test', 25, 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=25&query=test');
|
||||
});
|
||||
|
||||
test('with blank', () => {
|
||||
element.getRepos('test/test', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
|
||||
});
|
||||
|
||||
test('with hyphen', () => {
|
||||
element.getRepos('foo-bar', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||
});
|
||||
|
||||
test('with leading hyphen', () => {
|
||||
element.getRepos('-bar', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Abar');
|
||||
});
|
||||
|
||||
test('with trailing hyphen', () => {
|
||||
element.getRepos('foo-bar-', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||
});
|
||||
|
||||
test('with underscore', () => {
|
||||
element.getRepos('foo_bar', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||
});
|
||||
|
||||
test('with underscore', () => {
|
||||
element.getRepos('foo_bar', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||
});
|
||||
|
||||
test('hyphen only', () => {
|
||||
element.getRepos('-', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
||||
});
|
||||
});
|
||||
@@ -1012,45 +1113,43 @@ limitations under the License.
|
||||
});
|
||||
|
||||
suite('getGroups', () => {
|
||||
let fetchCacheURLStub;
|
||||
setup(() => {
|
||||
fetchCacheURLStub =
|
||||
sandbox.stub(element._restApiHelper, 'fetchCacheURL');
|
||||
sandbox.stub(element, '_fetchSharedCacheURL');
|
||||
});
|
||||
|
||||
test('normal use', () => {
|
||||
element.getGroups('test', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/groups/?n=26&S=0&m=test');
|
||||
|
||||
element.getGroups(null, 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/groups/?n=26&S=0');
|
||||
|
||||
element.getGroups('test', 25, 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/groups/?n=26&S=25&m=test');
|
||||
});
|
||||
|
||||
test('regex', () => {
|
||||
element.getGroups('^test.*', 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/groups/?n=26&S=0&r=%5Etest.*');
|
||||
|
||||
element.getGroups('^test.*', 25, 25);
|
||||
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
||||
'/groups/?n=26&S=25&r=%5Etest.*');
|
||||
});
|
||||
});
|
||||
|
||||
test('gerrit auth is used', () => {
|
||||
sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve());
|
||||
element._restApiHelper.fetchJSON({url: 'foo'});
|
||||
element._fetchJSON({url: 'foo'});
|
||||
assert(Gerrit.Auth.fetch.called);
|
||||
});
|
||||
|
||||
test('getSuggestedAccounts does not return _fetchJSON', () => {
|
||||
const _fetchJSONSpy = sandbox.spy(element._restApiHelper, 'fetchJSON');
|
||||
const _fetchJSONSpy = sandbox.spy(element, '_fetchJSON');
|
||||
return element.getSuggestedAccounts().then(accts => {
|
||||
assert.isFalse(_fetchJSONSpy.called);
|
||||
assert.equal(accts.length, 0);
|
||||
@@ -1058,7 +1157,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('_fetchJSON gets called by getSuggestedAccounts', () => {
|
||||
const _fetchJSONStub = sandbox.stub(element._restApiHelper, 'fetchJSON',
|
||||
const _fetchJSONStub = sandbox.stub(element, '_fetchJSON',
|
||||
() => Promise.resolve());
|
||||
return element.getSuggestedAccounts('own').then(() => {
|
||||
assert.deepEqual(_fetchJSONStub.lastCall.args[0].params, {
|
||||
@@ -1130,7 +1229,7 @@ limitations under the License.
|
||||
const errFn = sinon.stub();
|
||||
sandbox.stub(element, 'getChangeActionURL')
|
||||
.returns(Promise.resolve(''));
|
||||
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||
sandbox.stub(element, '_fetchRawJSON')
|
||||
.returns(Promise.resolve({ok: false, status: 500}));
|
||||
return element._getChangeDetail(123, '516714', errFn).then(() => {
|
||||
assert.isTrue(errFn.called);
|
||||
@@ -1150,12 +1249,11 @@ limitations under the License.
|
||||
test('_getChangeDetail populates _projectLookup', () => {
|
||||
sandbox.stub(element, 'getChangeActionURL')
|
||||
.returns(Promise.resolve(''));
|
||||
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||
sandbox.stub(element, '_fetchRawJSON')
|
||||
.returns(Promise.resolve({ok: true}));
|
||||
|
||||
const mockResponse = {_number: 1, project: 'test'};
|
||||
sandbox.stub(element._restApiHelper, 'readResponsePayload')
|
||||
.returns(Promise.resolve({
|
||||
sandbox.stub(element, '_readResponsePayload').returns(Promise.resolve({
|
||||
parsed: mockResponse,
|
||||
raw: JSON.stringify(mockResponse),
|
||||
}));
|
||||
@@ -1176,8 +1274,7 @@ limitations under the License.
|
||||
const mockResponse = {foo: 'bar', baz: 42};
|
||||
mockResponseSerial = element.JSON_PREFIX +
|
||||
JSON.stringify(mockResponse);
|
||||
sandbox.stub(element._restApiHelper, 'urlWithParams')
|
||||
.returns(requestUrl);
|
||||
sandbox.stub(element, '_urlWithParams').returns(requestUrl);
|
||||
sandbox.stub(element, 'getChangeActionURL')
|
||||
.returns(Promise.resolve(requestUrl));
|
||||
collectSpy = sandbox.spy(element._etags, 'collect');
|
||||
@@ -1185,8 +1282,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('contributes to cache', () => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||
.returns(Promise.resolve({
|
||||
sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
|
||||
text: () => Promise.resolve(mockResponseSerial),
|
||||
status: 200,
|
||||
ok: true,
|
||||
@@ -1201,8 +1297,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('uses cache on HTTP 304', () => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||
.returns(Promise.resolve({
|
||||
sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
|
||||
text: () => Promise.resolve(mockResponseSerial),
|
||||
status: 304,
|
||||
ok: true,
|
||||
@@ -1251,7 +1346,7 @@ limitations under the License.
|
||||
|
||||
suite('getChanges populates _projectLookup', () => {
|
||||
test('multiple queries', () => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||
sandbox.stub(element, '_fetchJSON')
|
||||
.returns(Promise.resolve([
|
||||
[
|
||||
{_number: 1, project: 'test'},
|
||||
@@ -1271,7 +1366,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('no query', () => {
|
||||
sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||
sandbox.stub(element, '_fetchJSON')
|
||||
.returns(Promise.resolve([
|
||||
{_number: 1, project: 'test'},
|
||||
{_number: 2, project: 'test'},
|
||||
@@ -1291,7 +1386,7 @@ limitations under the License.
|
||||
|
||||
test('_getChangeURLAndFetch', () => {
|
||||
element._projectLookup = {1: 'test'};
|
||||
const fetchStub = sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||
const fetchStub = sandbox.stub(element, '_fetchJSON')
|
||||
.returns(Promise.resolve());
|
||||
const req = {changeNum: 1, endpoint: '/test', patchNum: 1};
|
||||
return element._getChangeURLAndFetch(req).then(() => {
|
||||
@@ -1302,7 +1397,7 @@ limitations under the License.
|
||||
|
||||
test('_getChangeURLAndSend', () => {
|
||||
element._projectLookup = {1: 'test'};
|
||||
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||
const sendStub = sandbox.stub(element, '_send')
|
||||
.returns(Promise.resolve());
|
||||
|
||||
const req = {
|
||||
@@ -1324,8 +1419,7 @@ limitations under the License.
|
||||
const mockObject = {foo: 'bar', baz: 'foo'};
|
||||
const serial = element.JSON_PREFIX + JSON.stringify(mockObject);
|
||||
const mockResponse = {text: () => Promise.resolve(serial)};
|
||||
return element._restApiHelper.readResponsePayload(mockResponse)
|
||||
.then(payload => {
|
||||
return element._readResponsePayload(mockResponse).then(payload => {
|
||||
assert.deepEqual(payload.parsed, mockObject);
|
||||
assert.equal(payload.raw, serial);
|
||||
});
|
||||
@@ -1334,7 +1428,7 @@ limitations under the License.
|
||||
test('_parsePrefixedJSON', () => {
|
||||
const obj = {x: 3, y: {z: 4}, w: 23};
|
||||
const serial = element.JSON_PREFIX + JSON.stringify(obj);
|
||||
const result = element._restApiHelper.parsePrefixedJSON(serial);
|
||||
const result = element._parsePrefixedJSON(serial);
|
||||
assert.deepEqual(result, obj);
|
||||
});
|
||||
});
|
||||
@@ -1356,7 +1450,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('generateAccountHttpPassword', () => {
|
||||
const sendSpy = sandbox.spy(element._restApiHelper, 'send');
|
||||
const sendSpy = sandbox.spy(element, '_send');
|
||||
return element.generateAccountHttpPassword().then(() => {
|
||||
assert.isTrue(sendSpy.calledOnce);
|
||||
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
|
||||
@@ -1441,12 +1535,11 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('getDashboard', () => {
|
||||
const fetchCacheURLStub = sandbox.stub(element._restApiHelper,
|
||||
'fetchCacheURL');
|
||||
const fetchStub = sandbox.stub(element, '_fetchSharedCacheURL');
|
||||
element.getDashboard('gerrit/project', 'default:main');
|
||||
assert.isTrue(fetchCacheURLStub.calledOnce);
|
||||
assert.isTrue(fetchStub.calledOnce);
|
||||
assert.equal(
|
||||
fetchCacheURLStub.lastCall.args[0].url,
|
||||
fetchStub.lastCall.args[0].url,
|
||||
'/projects/gerrit%2Fproject/dashboards/default%3Amain');
|
||||
});
|
||||
|
||||
@@ -1514,7 +1607,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('_fetch forwards request and logs', () => {
|
||||
const logStub = sandbox.stub(element._restApiHelper, '_logCall');
|
||||
const logStub = sandbox.stub(element, '_logCall');
|
||||
const response = {status: 404, text: sinon.stub()};
|
||||
const url = 'my url';
|
||||
const fetchOptions = {method: 'DELETE'};
|
||||
@@ -1522,7 +1615,7 @@ limitations under the License.
|
||||
const startTime = 123;
|
||||
sandbox.stub(Date, 'now').returns(startTime);
|
||||
const req = {url, fetchOptions};
|
||||
return element._restApiHelper.fetch(req).then(() => {
|
||||
return element._fetch(req).then(() => {
|
||||
assert.isTrue(logStub.calledOnce);
|
||||
assert.isTrue(logStub.calledWith(req, startTime, response.status));
|
||||
assert.isFalse(response.text.called);
|
||||
@@ -1534,11 +1627,10 @@ limitations under the License.
|
||||
const handler = sinon.stub();
|
||||
element.addEventListener('rpc-log', handler);
|
||||
|
||||
element._restApiHelper._logCall({url: 'url'}, 100, 200);
|
||||
element._logCall({url: 'url'}, 100, 200);
|
||||
assert.isFalse(handler.called);
|
||||
|
||||
element._restApiHelper
|
||||
._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
|
||||
element._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(handler.calledOnce);
|
||||
});
|
||||
@@ -1547,7 +1639,7 @@ limitations under the License.
|
||||
sandbox.stub(element, 'getFromProjectLookup')
|
||||
.returns(Promise.resolve('test'));
|
||||
const sendStub =
|
||||
sandbox.stub(element._restApiHelper, 'send').returns(Promise.resolve());
|
||||
sandbox.stub(element, '_send').returns(Promise.resolve());
|
||||
|
||||
await element.saveChangeStarred(123, true);
|
||||
assert.isTrue(sendStub.calledOnce);
|
||||
|
||||
@@ -1,456 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
const Defs = {};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* url: string,
|
||||
* fetchOptions: (Object|null|undefined),
|
||||
* anonymizedUrl: (string|undefined),
|
||||
* }}
|
||||
*/
|
||||
Defs.FetchRequest;
|
||||
|
||||
/**
|
||||
* Object to describe a request for passing into fetchJSON or fetchRawJSON.
|
||||
* - url is the URL for the request (excluding get params)
|
||||
* - errFn is a function to invoke when the request fails.
|
||||
* - cancelCondition is a function that, if provided and returns true, will
|
||||
* cancel the response after it resolves.
|
||||
* - params is a key-value hash to specify get params for the request URL.
|
||||
* @typedef {{
|
||||
* url: string,
|
||||
* errFn: (function(?Response, string=)|null|undefined),
|
||||
* cancelCondition: (function()|null|undefined),
|
||||
* params: (Object|null|undefined),
|
||||
* fetchOptions: (Object|null|undefined),
|
||||
* anonymizedUrl: (string|undefined),
|
||||
* reportUrlAsIs: (boolean|undefined),
|
||||
* }}
|
||||
*/
|
||||
Defs.FetchJSONRequest;
|
||||
|
||||
const JSON_PREFIX = ')]}\'';
|
||||
const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
|
||||
|
||||
/**
|
||||
* Wrapper around Map for caching server responses. Site-based so that
|
||||
* changes to CANONICAL_PATH will result in a different cache going into
|
||||
* effect.
|
||||
*/
|
||||
class SiteBasedCache {
|
||||
constructor() {
|
||||
// Container of per-canonical-path caches.
|
||||
this._data = new Map();
|
||||
if (window.INITIAL_DATA != undefined) {
|
||||
// Put all data shipped with index.html into the cache. This makes it
|
||||
// so that we spare more round trips to the server when the app loads
|
||||
// initially.
|
||||
Object
|
||||
.entries(window.INITIAL_DATA)
|
||||
.forEach(e => this._cache().set(e[0], e[1]));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the cache for the current canonical path.
|
||||
_cache() {
|
||||
if (!this._data.has(window.CANONICAL_PATH)) {
|
||||
this._data.set(window.CANONICAL_PATH, new Map());
|
||||
}
|
||||
return this._data.get(window.CANONICAL_PATH);
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this._cache().has(key);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._cache().get(key);
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this._cache().set(key, value);
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
this._cache().delete(key);
|
||||
}
|
||||
|
||||
invalidatePrefix(prefix) {
|
||||
const newMap = new Map();
|
||||
for (const [key, value] of this._cache().entries()) {
|
||||
if (!key.startsWith(prefix)) {
|
||||
newMap.set(key, value);
|
||||
}
|
||||
}
|
||||
this._data.set(window.CANONICAL_PATH, newMap);
|
||||
}
|
||||
}
|
||||
|
||||
class FetchPromisesCache {
|
||||
constructor() {
|
||||
this._data = {};
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return !!this._data[key];
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._data[key];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this._data[key] = value;
|
||||
}
|
||||
|
||||
invalidatePrefix(prefix) {
|
||||
const newData = {};
|
||||
Object.entries(this._data).forEach(([key, value]) => {
|
||||
if (!key.startsWith(prefix)) {
|
||||
newData[key] = value;
|
||||
}
|
||||
});
|
||||
this._data = newData;
|
||||
}
|
||||
}
|
||||
|
||||
class GrRestApiHelper {
|
||||
/**
|
||||
* @param {SiteBasedCache} cache
|
||||
* @param {object} auth
|
||||
* @param {FetchPromisesCache} fetchPromisesCache
|
||||
* @param {object} credentialCheck
|
||||
* @param {object} restApiInterface
|
||||
*/
|
||||
constructor(cache, auth, fetchPromisesCache, credentialCheck,
|
||||
restApiInterface) {
|
||||
this._cache = cache;// TODO: make it public
|
||||
this._auth = auth;
|
||||
this._fetchPromisesCache = fetchPromisesCache;
|
||||
this._credentialCheck = credentialCheck;
|
||||
this._restApiInterface = restApiInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps calls to the underlying authenticated fetch function (_auth.fetch)
|
||||
* with timing and logging.
|
||||
* @param {Defs.FetchRequest} req
|
||||
*/
|
||||
fetch(req) {
|
||||
const start = Date.now();
|
||||
const xhr = this._auth.fetch(req.url, req.fetchOptions);
|
||||
|
||||
// Log the call after it completes.
|
||||
xhr.then(res => this._logCall(req, start, res ? res.status : null));
|
||||
|
||||
// Return the XHR directly (without the log).
|
||||
return xhr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log information about a REST call. Because the elapsed time is determined
|
||||
* by this method, it should be called immediately after the request
|
||||
* finishes.
|
||||
* @param {Defs.FetchRequest} req
|
||||
* @param {number} startTime the time that the request was started.
|
||||
* @param {number} status the HTTP status of the response. The status value
|
||||
* is used here rather than the response object so there is no way this
|
||||
* method can read the body stream.
|
||||
*/
|
||||
_logCall(req, startTime, status) {
|
||||
const method = (req.fetchOptions && req.fetchOptions.method) ?
|
||||
req.fetchOptions.method : 'GET';
|
||||
const endTime = Date.now();
|
||||
const elapsed = (endTime - startTime);
|
||||
const startAt = new Date(startTime);
|
||||
const endAt = new Date(endTime);
|
||||
console.log([
|
||||
'HTTP',
|
||||
status,
|
||||
method,
|
||||
elapsed + 'ms',
|
||||
req.anonymizedUrl || req.url,
|
||||
`(${startAt.toISOString()}, ${endAt.toISOString()})`,
|
||||
].join(' '));
|
||||
if (req.anonymizedUrl) {
|
||||
this.fire('rpc-log',
|
||||
{status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from url provided.
|
||||
* Returns a Promise that resolves to a native Response.
|
||||
* Doesn't do error checking. Supports cancel condition. Performs auth.
|
||||
* Validates auth expiry errors.
|
||||
* @param {Defs.FetchJSONRequest} req
|
||||
*/
|
||||
fetchRawJSON(req) {
|
||||
const urlWithParams = this.urlWithParams(req.url, req.params);
|
||||
const fetchReq = {
|
||||
url: urlWithParams,
|
||||
fetchOptions: req.fetchOptions,
|
||||
anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
|
||||
};
|
||||
return this.fetch(fetchReq).then(res => {
|
||||
if (req.cancelCondition && req.cancelCondition()) {
|
||||
res.body.cancel();
|
||||
return;
|
||||
}
|
||||
return res;
|
||||
}).catch(err => {
|
||||
const isLoggedIn = !!this._cache.get('/accounts/self/detail');
|
||||
if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) {
|
||||
this.checkCredentials();
|
||||
} else {
|
||||
if (req.errFn) {
|
||||
req.errFn.call(undefined, null, err);
|
||||
} else {
|
||||
this.fire('network-error', {error: err});
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from url provided.
|
||||
* Returns a Promise that resolves to a parsed response.
|
||||
* Same as {@link fetchRawJSON}, plus error handling.
|
||||
* @param {Defs.FetchJSONRequest} req
|
||||
*/
|
||||
fetchJSON(req) {
|
||||
req = this.addAcceptJsonHeader(req);
|
||||
return this.fetchRawJSON(req).then(response => {
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
if (req.errFn) {
|
||||
req.errFn.call(null, response);
|
||||
return;
|
||||
}
|
||||
this.fire('server-error', {request: req, response});
|
||||
return;
|
||||
}
|
||||
return response && this.getResponseObject(response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {?Object|string=} opt_params URL params, key-value hash.
|
||||
* @return {string}
|
||||
*/
|
||||
urlWithParams(url, opt_params) {
|
||||
if (!opt_params) { return this.getBaseUrl() + url; }
|
||||
|
||||
const params = [];
|
||||
for (const p in opt_params) {
|
||||
if (!opt_params.hasOwnProperty(p)) { continue; }
|
||||
if (opt_params[p] == null) {
|
||||
params.push(encodeURIComponent(p));
|
||||
continue;
|
||||
}
|
||||
for (const value of [].concat(opt_params[p])) {
|
||||
params.push(`${encodeURIComponent(p)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return this.getBaseUrl() + url + '?' + params.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} response
|
||||
* @return {?}
|
||||
*/
|
||||
getResponseObject(response) {
|
||||
return this.readResponsePayload(response)
|
||||
.then(payload => payload.parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} response
|
||||
* @return {!Object}
|
||||
*/
|
||||
readResponsePayload(response) {
|
||||
return response.text().then(text => {
|
||||
let result;
|
||||
try {
|
||||
result = this.parsePrefixedJSON(text);
|
||||
} catch (_) {
|
||||
result = null;
|
||||
}
|
||||
return {parsed: result, raw: text};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @return {?}
|
||||
*/
|
||||
parsePrefixedJSON(source) {
|
||||
return JSON.parse(source.substring(JSON_PREFIX.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Defs.FetchJSONRequest} req
|
||||
* @return {Defs.FetchJSONRequest}
|
||||
*/
|
||||
addAcceptJsonHeader(req) {
|
||||
if (!req.fetchOptions) req.fetchOptions = {};
|
||||
if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
|
||||
if (!req.fetchOptions.headers.has('Accept')) {
|
||||
req.fetchOptions.headers.append('Accept', 'application/json');
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
getBaseUrl() {
|
||||
return this._restApiInterface.getBaseUrl();
|
||||
}
|
||||
|
||||
fire(type, detail, options) {
|
||||
return this._restApiInterface.fire(type, detail, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Defs.FetchJSONRequest} req
|
||||
*/
|
||||
fetchCacheURL(req) {
|
||||
if (this._fetchPromisesCache.has(req.url)) {
|
||||
return this._fetchPromisesCache.get(req.url);
|
||||
}
|
||||
// TODO(andybons): Periodic cache invalidation.
|
||||
if (this._cache.has(req.url)) {
|
||||
return Promise.resolve(this._cache.get(req.url));
|
||||
}
|
||||
this._fetchPromisesCache.set(req.url,
|
||||
this.fetchJSON(req).then(response => {
|
||||
if (response !== undefined) {
|
||||
this._cache.set(req.url, response);
|
||||
}
|
||||
this._fetchPromisesCache.set(req.url, undefined);
|
||||
return response;
|
||||
}).catch(err => {
|
||||
this._fetchPromisesCache.set(req.url, undefined);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
return this._fetchPromisesCache.get(req.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an XHR.
|
||||
* @param {Defs.SendRequest} req
|
||||
* @return {Promise}
|
||||
*/
|
||||
send(req) {
|
||||
const options = {method: req.method};
|
||||
if (req.body) {
|
||||
options.headers = new Headers();
|
||||
options.headers.set(
|
||||
'Content-Type', req.contentType || 'application/json');
|
||||
options.body = typeof req.body === 'string' ?
|
||||
req.body : JSON.stringify(req.body);
|
||||
}
|
||||
if (req.headers) {
|
||||
if (!options.headers) { options.headers = new Headers(); }
|
||||
for (const header in req.headers) {
|
||||
if (!req.headers.hasOwnProperty(header)) { continue; }
|
||||
options.headers.set(header, req.headers[header]);
|
||||
}
|
||||
}
|
||||
const url = req.url.startsWith('http') ?
|
||||
req.url : this.getBaseUrl() + req.url;
|
||||
const fetchReq = {
|
||||
url,
|
||||
fetchOptions: options,
|
||||
anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
|
||||
};
|
||||
const xhr = this.fetch(fetchReq).then(response => {
|
||||
if (!response.ok) {
|
||||
if (req.errFn) {
|
||||
return req.errFn.call(undefined, response);
|
||||
}
|
||||
this.fire('server-error', {request: fetchReq, response});
|
||||
}
|
||||
return response;
|
||||
}).catch(err => {
|
||||
this.fire('network-error', {error: err});
|
||||
if (req.errFn) {
|
||||
return req.errFn.call(undefined, null, err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
if (req.parseResponse) {
|
||||
return xhr.then(res => this.getResponseObject(res));
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
checkCredentials() {
|
||||
if (this._credentialCheck.checking) {
|
||||
return;
|
||||
}
|
||||
this._credentialCheck.checking = true;
|
||||
let req = {url: '/accounts/self/detail', reportUrlAsIs: true};
|
||||
req = this.addAcceptJsonHeader(req);
|
||||
// Skip the REST response cache.
|
||||
return this.fetchRawJSON(req).then(res => {
|
||||
if (!res) { return; }
|
||||
if (res.status === 403) {
|
||||
this.fire('auth-error');
|
||||
this._cache.delete('/accounts/self/detail');
|
||||
} else if (res.ok) {
|
||||
return this.getResponseObject(res);
|
||||
}
|
||||
}).then(res => {
|
||||
this._credentialCheck.checking = false;
|
||||
if (res) {
|
||||
this._cache.set('/accounts/self/detail', res);
|
||||
}
|
||||
return res;
|
||||
}).catch(err => {
|
||||
this._credentialCheck.checking = false;
|
||||
if (err && err.message === FAILED_TO_FETCH_ERROR) {
|
||||
this.fire('auth-error');
|
||||
this._cache.delete('/accounts/self/detail');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} prefix
|
||||
*/
|
||||
invalidateFetchPromisesPrefix(prefix) {
|
||||
this._fetchPromisesCache.invalidatePrefix(prefix);
|
||||
this._cache.invalidatePrefix(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
window.SiteBasedCache = SiteBasedCache;
|
||||
window.FetchPromisesCache = FetchPromisesCache;
|
||||
window.GrRestApiHelper = GrRestApiHelper;
|
||||
})(window);
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-rest-api-helper</title>
|
||||
<script src="/test/common-test-setup.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<link rel="import" href="../../../../test/common-test-setup.html"/>
|
||||
<script src="../../../../scripts/util.js"></script>
|
||||
<script src="../gr-auth.js"></script>
|
||||
<script src="gr-rest-api-helper.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<script>
|
||||
suite('gr-rest-api-helper tests', () => {
|
||||
let helper;
|
||||
let sandbox;
|
||||
let cache;
|
||||
let fetchPromisesCache;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
cache = new SiteBasedCache();
|
||||
fetchPromisesCache = new FetchPromisesCache();
|
||||
const credentialCheck = {checking: false};
|
||||
|
||||
window.CANONICAL_PATH = 'testhelper';
|
||||
|
||||
const mockRestApiInterface = {
|
||||
getBaseUrl: sinon.stub().returns(window.CANONICAL_PATH),
|
||||
fire: sinon.stub(),
|
||||
};
|
||||
|
||||
const testJSON = ')]}\'\n{"hello": "bonjour"}';
|
||||
sandbox.stub(window, 'fetch').returns(Promise.resolve({
|
||||
ok: true,
|
||||
text() {
|
||||
return Promise.resolve(testJSON);
|
||||
},
|
||||
}));
|
||||
|
||||
helper = new GrRestApiHelper(cache, Gerrit.Auth, fetchPromisesCache,
|
||||
credentialCheck, mockRestApiInterface);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('fetchJSON()', () => {
|
||||
test('Sets header to accept application/json', () => {
|
||||
const authFetchStub = sandbox.stub(helper._auth, 'fetch')
|
||||
.returns(Promise.resolve());
|
||||
helper.fetchJSON({url: '/dummy/url'});
|
||||
assert.isTrue(authFetchStub.called);
|
||||
assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
|
||||
'application/json');
|
||||
});
|
||||
|
||||
test('Use header option accept when provided', () => {
|
||||
const authFetchStub = sandbox.stub(helper._auth, 'fetch')
|
||||
.returns(Promise.resolve());
|
||||
const headers = new Headers();
|
||||
headers.append('Accept', '*/*');
|
||||
const fetchOptions = {headers};
|
||||
helper.fetchJSON({url: '/dummy/url', fetchOptions});
|
||||
assert.isTrue(authFetchStub.called);
|
||||
assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
|
||||
'*/*');
|
||||
});
|
||||
});
|
||||
|
||||
test('JSON prefix is properly removed', done => {
|
||||
helper.fetchJSON({url: '/dummy/url'}).then(obj => {
|
||||
assert.deepEqual(obj, {hello: 'bonjour'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('cached results', done => {
|
||||
let n = 0;
|
||||
sandbox.stub(helper, 'fetchJSON', () => {
|
||||
return Promise.resolve(++n);
|
||||
});
|
||||
const promises = [];
|
||||
promises.push(helper.fetchCacheURL('/foo'));
|
||||
promises.push(helper.fetchCacheURL('/foo'));
|
||||
promises.push(helper.fetchCacheURL('/foo'));
|
||||
|
||||
Promise.all(promises).then(results => {
|
||||
assert.deepEqual(results, [1, 1, 1]);
|
||||
helper.fetchCacheURL('/foo').then(foo => {
|
||||
assert.equal(foo, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('cached promise', done => {
|
||||
const promise = Promise.reject(new Error('foo'));
|
||||
cache.set('/foo', promise);
|
||||
helper.fetchCacheURL({url: '/foo'}).catch(p => {
|
||||
assert.equal(p.message, 'foo');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('cache invalidation', () => {
|
||||
cache.set('/foo/bar', 1);
|
||||
cache.set('/bar', 2);
|
||||
fetchPromisesCache.set('/foo/bar', 3);
|
||||
fetchPromisesCache.set('/bar', 4);
|
||||
helper.invalidateFetchPromisesPrefix('/foo/');
|
||||
assert.isFalse(cache.has('/foo/bar'));
|
||||
assert.isTrue(cache.has('/bar'));
|
||||
assert.isUndefined(fetchPromisesCache.get('/foo/bar'));
|
||||
assert.strictEqual(4, fetchPromisesCache.get('/bar'));
|
||||
});
|
||||
|
||||
test('params are properly encoded', () => {
|
||||
let url = helper.urlWithParams('/path/', {
|
||||
sp: 'hola',
|
||||
gr: 'guten tag',
|
||||
noval: null,
|
||||
});
|
||||
assert.equal(url,
|
||||
window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
|
||||
|
||||
url = helper.urlWithParams('/path/', {
|
||||
sp: 'hola',
|
||||
en: ['hey', 'hi'],
|
||||
});
|
||||
assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
|
||||
|
||||
// Order must be maintained with array params.
|
||||
url = helper.urlWithParams('/path/', {
|
||||
l: ['c', 'b', 'a'],
|
||||
});
|
||||
assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
|
||||
});
|
||||
|
||||
test('request callbacks can be canceled', done => {
|
||||
let cancelCalled = false;
|
||||
window.fetch.returns(Promise.resolve({
|
||||
body: {
|
||||
cancel() { cancelCalled = true; },
|
||||
},
|
||||
}));
|
||||
const cancelCondition = () => { return true; };
|
||||
helper.fetchJSON({url: '/dummy/url', cancelCondition}).then(
|
||||
obj => {
|
||||
assert.isUndefined(obj);
|
||||
assert.isTrue(cancelCalled);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -35,9 +35,6 @@ const EXTERN_NAMES = [
|
||||
'GrReviewerUpdatesParser',
|
||||
'GrCountStringFormatter',
|
||||
'GrThemeApi',
|
||||
'SiteBasedCache',
|
||||
'FetchPromisesCache',
|
||||
'GrRestApiHelper',
|
||||
'moment',
|
||||
'page',
|
||||
'util',
|
||||
|
||||
@@ -187,7 +187,6 @@ limitations under the License.
|
||||
'shared/gr-rest-api-interface/gr-auth_test.html',
|
||||
'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
|
||||
'shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html',
|
||||
'shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html',
|
||||
'shared/gr-select/gr-select_test.html',
|
||||
'shared/gr-storage/gr-storage_test.html',
|
||||
'shared/gr-textarea/gr-textarea_test.html',
|
||||
|
||||
Reference in New Issue
Block a user