Refactor gr-rest-api-interface - extract common methods to helper
gr-rest-api-interface contains api for different entities types and all the code is placed in one file - gr-rest-api-interface.js. This commit moves all utils methods to a separate GrRestApiHelper class. After moving, new API can be added as a separate file. In the future existing API can be refactored as well to provide more structured access to API. For example, repo API can be exposed as restAPI.repositories.getConfig(...) Change-Id: I57f52847aa7e0d4f924f3f5105fc64cd0226d2ff
This commit is contained in:
@@ -29,6 +29,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<dom-module id="gr-rest-api-interface">
|
<dom-module id="gr-rest-api-interface">
|
||||||
<!-- NB: Order is important, because of namespaced classes. -->
|
<!-- 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-auth.js"></script>
|
||||||
<script src="gr-reviewer-updates-parser.js"></script>
|
<script src="gr-reviewer-updates-parser.js"></script>
|
||||||
<script src="gr-rest-api-interface.js"></script>
|
<script src="gr-rest-api-interface.js"></script>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -63,116 +63,8 @@ limitations under the License.
|
|||||||
sandbox.restore();
|
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 => {
|
test('parent diff comments are properly grouped', done => {
|
||||||
sandbox.stub(element, '_fetchJSON', () => {
|
sandbox.stub(element._restApiHelper, 'fetchJSON', () => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
'/COMMIT_MSG': [],
|
'/COMMIT_MSG': [],
|
||||||
'sieve.go': [
|
'sieve.go': [
|
||||||
@@ -315,7 +207,7 @@ limitations under the License.
|
|||||||
test('differing patch diff comments are properly grouped', done => {
|
test('differing patch diff comments are properly grouped', done => {
|
||||||
sandbox.stub(element, 'getFromProjectLookup')
|
sandbox.stub(element, 'getFromProjectLookup')
|
||||||
.returns(Promise.resolve('test'));
|
.returns(Promise.resolve('test'));
|
||||||
sandbox.stub(element, '_fetchJSON', request => {
|
sandbox.stub(element._restApiHelper, 'fetchJSON', request => {
|
||||||
const url = request.url;
|
const url = request.url;
|
||||||
if (url === '/changes/test~42/revisions/1') {
|
if (url === '/changes/test~42/revisions/1') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -432,7 +324,7 @@ limitations under the License.
|
|||||||
suite('rebase action', () => {
|
suite('rebase action', () => {
|
||||||
let resolve_fetchJSON;
|
let resolve_fetchJSON;
|
||||||
setup(() => {
|
setup(() => {
|
||||||
sandbox.stub(element, '_fetchJSON').returns(
|
sandbox.stub(element._restApiHelper, 'fetchJSON').returns(
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
resolve_fetchJSON = resolve;
|
resolve_fetchJSON = resolve;
|
||||||
}));
|
}));
|
||||||
@@ -467,7 +359,7 @@ limitations under the License.
|
|||||||
element.addEventListener('server-error', resolve);
|
element.addEventListener('server-error', resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
element._fetchJSON({}).then(response => {
|
element._restApiHelper.fetchJSON({}).then(response => {
|
||||||
assert.isUndefined(response);
|
assert.isUndefined(response);
|
||||||
assert.isTrue(getResponseObjectStub.notCalled);
|
assert.isTrue(getResponseObjectStub.notCalled);
|
||||||
serverErrorEventPromise.then(() => done());
|
serverErrorEventPromise.then(() => done());
|
||||||
@@ -483,12 +375,12 @@ limitations under the License.
|
|||||||
Promise.reject(new Error('Failed to fetch')));
|
Promise.reject(new Error('Failed to fetch')));
|
||||||
window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse));
|
window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse));
|
||||||
// Emulate logged in.
|
// Emulate logged in.
|
||||||
element._cache.set('/accounts/self/detail', {});
|
element._restApiHelper._cache.set('/accounts/self/detail', {});
|
||||||
const serverErrorStub = sandbox.stub();
|
const serverErrorStub = sandbox.stub();
|
||||||
element.addEventListener('server-error', serverErrorStub);
|
element.addEventListener('server-error', serverErrorStub);
|
||||||
const authErrorStub = sandbox.stub();
|
const authErrorStub = sandbox.stub();
|
||||||
element.addEventListener('auth-error', authErrorStub);
|
element.addEventListener('auth-error', authErrorStub);
|
||||||
element._fetchJSON({url: '/bar'}).finally(r => {
|
element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
|
||||||
flush(() => {
|
flush(() => {
|
||||||
assert.isTrue(authErrorStub.called);
|
assert.isTrue(authErrorStub.called);
|
||||||
assert.isFalse(serverErrorStub.called);
|
assert.isFalse(serverErrorStub.called);
|
||||||
@@ -507,7 +399,7 @@ limitations under the License.
|
|||||||
element.addEventListener('server-error', serverErrorStub);
|
element.addEventListener('server-error', serverErrorStub);
|
||||||
const authErrorStub = sandbox.stub();
|
const authErrorStub = sandbox.stub();
|
||||||
element.addEventListener('auth-error', authErrorStub);
|
element.addEventListener('auth-error', authErrorStub);
|
||||||
element._fetchJSON({url: '/bar'}).finally(r => {
|
element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
|
||||||
flush(() => {
|
flush(() => {
|
||||||
assert.isTrue(authErrorStub.called);
|
assert.isTrue(authErrorStub.called);
|
||||||
assert.isFalse(serverErrorStub.called);
|
assert.isFalse(serverErrorStub.called);
|
||||||
@@ -558,7 +450,8 @@ limitations under the License.
|
|||||||
test('checkCredentials promise rejection', () => {
|
test('checkCredentials promise rejection', () => {
|
||||||
window.fetch.restore();
|
window.fetch.restore();
|
||||||
element._cache.set('/accounts/self/detail', true);
|
element._cache.set('/accounts/self/detail', true);
|
||||||
sandbox.spy(element, 'checkCredentials');
|
const checkCredentialsSpy =
|
||||||
|
sandbox.spy(element._restApiHelper, 'checkCredentials');
|
||||||
sandbox.stub(window, 'fetch', url => {
|
sandbox.stub(window, 'fetch', url => {
|
||||||
return Promise.reject(new Error('Failed to fetch'));
|
return Promise.reject(new Error('Failed to fetch'));
|
||||||
});
|
});
|
||||||
@@ -570,7 +463,7 @@ limitations under the License.
|
|||||||
// The second fetch call also fails, which leads to a second
|
// The second fetch call also fails, which leads to a second
|
||||||
// invocation of checkCredentials, which should immediately
|
// invocation of checkCredentials, which should immediately
|
||||||
// return instead of making further fetch calls.
|
// return instead of making further fetch calls.
|
||||||
assert.isTrue(element.checkCredentials.calledTwice);
|
assert.isTrue(checkCredentialsSpy .calledTwice);
|
||||||
assert.isTrue(window.fetch.calledTwice);
|
assert.isTrue(window.fetch.calledTwice);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -585,7 +478,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('legacy n,z key in change url is replaced', () => {
|
test('legacy n,z key in change url is replaced', () => {
|
||||||
const stub = sandbox.stub(element, '_fetchJSON')
|
const stub = sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||||
.returns(Promise.resolve([]));
|
.returns(Promise.resolve([]));
|
||||||
element.getChanges(1, null, 'n,z');
|
element.getChanges(1, null, 'n,z');
|
||||||
assert.equal(stub.lastCall.args[0].params.S, 0);
|
assert.equal(stub.lastCall.args[0].params.S, 0);
|
||||||
@@ -593,38 +486,38 @@ limitations under the License.
|
|||||||
|
|
||||||
test('saveDiffPreferences invalidates cache line', () => {
|
test('saveDiffPreferences invalidates cache line', () => {
|
||||||
const cacheKey = '/accounts/self/preferences.diff';
|
const cacheKey = '/accounts/self/preferences.diff';
|
||||||
sandbox.stub(element, '_send');
|
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||||
element._cache.set(cacheKey, {tab_size: 4});
|
element._cache.set(cacheKey, {tab_size: 4});
|
||||||
element.saveDiffPreferences({tab_size: 8});
|
element.saveDiffPreferences({tab_size: 8});
|
||||||
assert.isTrue(element._send.called);
|
assert.isTrue(sendStub.called);
|
||||||
assert.isFalse(element._cache.has(cacheKey));
|
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getAccount when resp is null does not add anything to the cache',
|
test('getAccount when resp is null does not add anything to the cache',
|
||||||
done => {
|
done => {
|
||||||
const cacheKey = '/accounts/self/detail';
|
const cacheKey = '/accounts/self/detail';
|
||||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||||
() => Promise.resolve());
|
() => Promise.resolve());
|
||||||
|
|
||||||
element.getAccount().then(() => {
|
element.getAccount().then(() => {
|
||||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
assert.isTrue(stub.called);
|
||||||
assert.isFalse(element._cache.has(cacheKey));
|
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
element._cache.set(cacheKey, 'fake cache');
|
element._restApiHelper._cache.set(cacheKey, 'fake cache');
|
||||||
stub.lastCall.args[0].errFn();
|
stub.lastCall.args[0].errFn();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getAccount does not add to the cache when resp.status is 403',
|
test('getAccount does not add to the cache when resp.status is 403',
|
||||||
done => {
|
done => {
|
||||||
const cacheKey = '/accounts/self/detail';
|
const cacheKey = '/accounts/self/detail';
|
||||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||||
() => Promise.resolve());
|
() => Promise.resolve());
|
||||||
|
|
||||||
element.getAccount().then(() => {
|
element.getAccount().then(() => {
|
||||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
assert.isTrue(stub.called);
|
||||||
assert.isFalse(element._cache.has(cacheKey));
|
assert.isFalse(element._restApiHelper._cache.has(cacheKey));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
element._cache.set(cacheKey, 'fake cache');
|
element._cache.set(cacheKey, 'fake cache');
|
||||||
@@ -633,15 +526,15 @@ limitations under the License.
|
|||||||
|
|
||||||
test('getAccount when resp is successful', done => {
|
test('getAccount when resp is successful', done => {
|
||||||
const cacheKey = '/accounts/self/detail';
|
const cacheKey = '/accounts/self/detail';
|
||||||
const stub = sandbox.stub(element, '_fetchSharedCacheURL',
|
const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
|
||||||
() => Promise.resolve());
|
() => Promise.resolve());
|
||||||
|
|
||||||
element.getAccount().then(response => {
|
element.getAccount().then(response => {
|
||||||
assert.isTrue(element._fetchSharedCacheURL.called);
|
assert.isTrue(stub.called);
|
||||||
assert.equal(element._cache.get(cacheKey), 'fake cache');
|
assert.equal(element._restApiHelper._cache.get(cacheKey), 'fake cache');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
element._cache.set(cacheKey, 'fake cache');
|
element._restApiHelper._cache.set(cacheKey, 'fake cache');
|
||||||
|
|
||||||
stub.lastCall.args[0].errFn({});
|
stub.lastCall.args[0].errFn({});
|
||||||
});
|
});
|
||||||
@@ -653,7 +546,7 @@ limitations under the License.
|
|||||||
sandbox.stub(element, '_isNarrowScreen', () => {
|
sandbox.stub(element, '_isNarrowScreen', () => {
|
||||||
return smallScreen;
|
return smallScreen;
|
||||||
});
|
});
|
||||||
sandbox.stub(element, '_fetchSharedCacheURL', () => {
|
sandbox.stub(element._restApiHelper, 'fetchCacheURL', () => {
|
||||||
return Promise.resolve(testJSON);
|
return Promise.resolve(testJSON);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -718,10 +611,10 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('savPreferences normalizes download scheme', () => {
|
test('savPreferences normalizes download scheme', () => {
|
||||||
sandbox.stub(element, '_send');
|
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||||
element.savePreferences({download_scheme: 'HTTP'});
|
element.savePreferences({download_scheme: 'HTTP'});
|
||||||
assert.isTrue(element._send.called);
|
assert.isTrue(sendStub.called);
|
||||||
assert.equal(element._send.lastCall.args[0].body.download_scheme, 'http');
|
assert.equal(sendStub.lastCall.args[0].body.download_scheme, 'http');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDiffPreferences returns correct defaults', done => {
|
test('getDiffPreferences returns correct defaults', done => {
|
||||||
@@ -747,10 +640,10 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('saveDiffPreferences set show_tabs to false', () => {
|
test('saveDiffPreferences set show_tabs to false', () => {
|
||||||
sandbox.stub(element, '_send');
|
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||||
element.saveDiffPreferences({show_tabs: false});
|
element.saveDiffPreferences({show_tabs: false});
|
||||||
assert.isTrue(element._send.called);
|
assert.isTrue(sendStub.called);
|
||||||
assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
|
assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getEditPreferences returns correct defaults', done => {
|
test('getEditPreferences returns correct defaults', done => {
|
||||||
@@ -780,33 +673,35 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('saveEditPreferences set show_tabs to false', () => {
|
test('saveEditPreferences set show_tabs to false', () => {
|
||||||
sandbox.stub(element, '_send');
|
const sendStub = sandbox.stub(element._restApiHelper, 'send');
|
||||||
element.saveEditPreferences({show_tabs: false});
|
element.saveEditPreferences({show_tabs: false});
|
||||||
assert.isTrue(element._send.called);
|
assert.isTrue(sendStub.called);
|
||||||
assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
|
assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('confirmEmail', () => {
|
test('confirmEmail', () => {
|
||||||
sandbox.spy(element, '_send');
|
const sendStub = sandbox.spy(element._restApiHelper, 'send');
|
||||||
element.confirmEmail('foo');
|
element.confirmEmail('foo');
|
||||||
assert.isTrue(element._send.calledOnce);
|
assert.isTrue(sendStub.calledOnce);
|
||||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
assert.equal(sendStub.lastCall.args[0].method, 'PUT');
|
||||||
assert.equal(element._send.lastCall.args[0].url,
|
assert.equal(sendStub.lastCall.args[0].url,
|
||||||
'/config/server/email.confirm');
|
'/config/server/email.confirm');
|
||||||
assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
|
assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setAccountStatus', () => {
|
test('setAccountStatus', () => {
|
||||||
sandbox.stub(element, '_send').returns(Promise.resolve('OOO'));
|
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||||
|
.returns(Promise.resolve('OOO'));
|
||||||
element._cache.set('/accounts/self/detail', {});
|
element._cache.set('/accounts/self/detail', {});
|
||||||
return element.setAccountStatus('OOO').then(() => {
|
return element.setAccountStatus('OOO').then(() => {
|
||||||
assert.isTrue(element._send.calledOnce);
|
assert.isTrue(sendStub.calledOnce);
|
||||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
assert.equal(sendStub.lastCall.args[0].method, 'PUT');
|
||||||
assert.equal(element._send.lastCall.args[0].url,
|
assert.equal(sendStub.lastCall.args[0].url,
|
||||||
'/accounts/self/status');
|
'/accounts/self/status');
|
||||||
assert.deepEqual(element._send.lastCall.args[0].body,
|
assert.deepEqual(sendStub.lastCall.args[0].body,
|
||||||
{status: 'OOO'});
|
{status: 'OOO'});
|
||||||
assert.deepEqual(element._cache.get('/accounts/self/detail'),
|
assert.deepEqual(element._restApiHelper
|
||||||
|
._cache.get('/accounts/self/detail'),
|
||||||
{status: 'OOO'});
|
{status: 'OOO'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -896,18 +791,20 @@ limitations under the License.
|
|||||||
const change_num = '1';
|
const change_num = '1';
|
||||||
const file_name = 'index.php';
|
const file_name = 'index.php';
|
||||||
const file_contents = '<?php';
|
const file_contents = '<?php';
|
||||||
sandbox.stub(element, '_send').returns(
|
sandbox.stub(element._restApiHelper, 'send').returns(
|
||||||
Promise.resolve([change_num, file_name, file_contents]));
|
Promise.resolve([change_num, file_name, file_contents]));
|
||||||
sandbox.stub(element, 'getResponseObject')
|
sandbox.stub(element, 'getResponseObject')
|
||||||
.returns(Promise.resolve([change_num, file_name, file_contents]));
|
.returns(Promise.resolve([change_num, file_name, file_contents]));
|
||||||
element._cache.set('/changes/' + change_num + '/edit/' + file_name, {});
|
element._cache.set('/changes/' + change_num + '/edit/' + file_name, {});
|
||||||
return element.saveChangeEdit(change_num, file_name, file_contents)
|
return element.saveChangeEdit(change_num, file_name, file_contents)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
assert.isTrue(element._send.calledOnce);
|
assert.isTrue(element._restApiHelper.send.calledOnce);
|
||||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
assert.equal(element._restApiHelper.send.lastCall.args[0].method,
|
||||||
assert.equal(element._send.lastCall.args[0].url,
|
'PUT');
|
||||||
|
assert.equal(element._restApiHelper.send.lastCall.args[0].url,
|
||||||
'/changes/test~1/edit/' + file_name);
|
'/changes/test~1/edit/' + file_name);
|
||||||
assert.equal(element._send.lastCall.args[0].body, file_contents);
|
assert.equal(element._restApiHelper.send.lastCall.args[0].body,
|
||||||
|
file_contents);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -915,17 +812,18 @@ limitations under the License.
|
|||||||
element._projectLookup = {1: 'test'};
|
element._projectLookup = {1: 'test'};
|
||||||
const change_num = '1';
|
const change_num = '1';
|
||||||
const message = 'this is a commit message';
|
const message = 'this is a commit message';
|
||||||
sandbox.stub(element, '_send').returns(
|
sandbox.stub(element._restApiHelper, 'send').returns(
|
||||||
Promise.resolve([change_num, message]));
|
Promise.resolve([change_num, message]));
|
||||||
sandbox.stub(element, 'getResponseObject')
|
sandbox.stub(element, 'getResponseObject')
|
||||||
.returns(Promise.resolve([change_num, message]));
|
.returns(Promise.resolve([change_num, message]));
|
||||||
element._cache.set('/changes/' + change_num + '/message', {});
|
element._cache.set('/changes/' + change_num + '/message', {});
|
||||||
return element.putChangeCommitMessage(change_num, message).then(() => {
|
return element.putChangeCommitMessage(change_num, message).then(() => {
|
||||||
assert.isTrue(element._send.calledOnce);
|
assert.isTrue(element._restApiHelper.send.calledOnce);
|
||||||
assert.equal(element._send.lastCall.args[0].method, 'PUT');
|
assert.equal(element._restApiHelper.send.lastCall.args[0].method, 'PUT');
|
||||||
assert.equal(element._send.lastCall.args[0].url,
|
assert.equal(element._restApiHelper.send.lastCall.args[0].url,
|
||||||
'/changes/test~1/message');
|
'/changes/test~1/message');
|
||||||
assert.deepEqual(element._send.lastCall.args[0].body, {message});
|
assert.deepEqual(element._restApiHelper.send.lastCall.args[0].body,
|
||||||
|
{message});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -981,7 +879,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('createRepo encodes name', () => {
|
test('createRepo encodes name', () => {
|
||||||
const sendStub = sandbox.stub(element, '_send')
|
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||||
.returns(Promise.resolve());
|
.returns(Promise.resolve());
|
||||||
return element.createRepo({name: 'x/y'}).then(() => {
|
return element.createRepo({name: 'x/y'}).then(() => {
|
||||||
assert.isTrue(sendStub.calledOnce);
|
assert.isTrue(sendStub.calledOnce);
|
||||||
@@ -1027,64 +925,65 @@ limitations under the License.
|
|||||||
|
|
||||||
suite('getRepos', () => {
|
suite('getRepos', () => {
|
||||||
const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
|
const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
|
||||||
|
let fetchCacheURLStub;
|
||||||
setup(() => {
|
setup(() => {
|
||||||
sandbox.stub(element, '_fetchSharedCacheURL');
|
fetchCacheURLStub =
|
||||||
|
sandbox.stub(element._restApiHelper, 'fetchCacheURL');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normal use', () => {
|
test('normal use', () => {
|
||||||
element.getRepos('test', 25);
|
element.getRepos('test', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=test');
|
'/projects/?n=26&S=0&query=test');
|
||||||
|
|
||||||
element.getRepos(null, 25);
|
element.getRepos(null, 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
||||||
|
|
||||||
element.getRepos('test', 25, 25);
|
element.getRepos('test', 25, 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=25&query=test');
|
'/projects/?n=26&S=25&query=test');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with blank', () => {
|
test('with blank', () => {
|
||||||
element.getRepos('test/test', 25);
|
element.getRepos('test/test', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
|
'/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with hyphen', () => {
|
test('with hyphen', () => {
|
||||||
element.getRepos('foo-bar', 25);
|
element.getRepos('foo-bar', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with leading hyphen', () => {
|
test('with leading hyphen', () => {
|
||||||
element.getRepos('-bar', 25);
|
element.getRepos('-bar', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Abar');
|
'/projects/?n=26&S=0&query=inname%3Abar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with trailing hyphen', () => {
|
test('with trailing hyphen', () => {
|
||||||
element.getRepos('foo-bar-', 25);
|
element.getRepos('foo-bar-', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with underscore', () => {
|
test('with underscore', () => {
|
||||||
element.getRepos('foo_bar', 25);
|
element.getRepos('foo_bar', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with underscore', () => {
|
test('with underscore', () => {
|
||||||
element.getRepos('foo_bar', 25);
|
element.getRepos('foo_bar', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hyphen only', () => {
|
test('hyphen only', () => {
|
||||||
element.getRepos('-', 25);
|
element.getRepos('-', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
`/projects/?n=26&S=0&query=${defaultQuery}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1113,43 +1012,45 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
suite('getGroups', () => {
|
suite('getGroups', () => {
|
||||||
|
let fetchCacheURLStub;
|
||||||
setup(() => {
|
setup(() => {
|
||||||
sandbox.stub(element, '_fetchSharedCacheURL');
|
fetchCacheURLStub =
|
||||||
|
sandbox.stub(element._restApiHelper, 'fetchCacheURL');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normal use', () => {
|
test('normal use', () => {
|
||||||
element.getGroups('test', 25);
|
element.getGroups('test', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/groups/?n=26&S=0&m=test');
|
'/groups/?n=26&S=0&m=test');
|
||||||
|
|
||||||
element.getGroups(null, 25);
|
element.getGroups(null, 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/groups/?n=26&S=0');
|
'/groups/?n=26&S=0');
|
||||||
|
|
||||||
element.getGroups('test', 25, 25);
|
element.getGroups('test', 25, 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/groups/?n=26&S=25&m=test');
|
'/groups/?n=26&S=25&m=test');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('regex', () => {
|
test('regex', () => {
|
||||||
element.getGroups('^test.*', 25);
|
element.getGroups('^test.*', 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/groups/?n=26&S=0&r=%5Etest.*');
|
'/groups/?n=26&S=0&r=%5Etest.*');
|
||||||
|
|
||||||
element.getGroups('^test.*', 25, 25);
|
element.getGroups('^test.*', 25, 25);
|
||||||
assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
|
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/groups/?n=26&S=25&r=%5Etest.*');
|
'/groups/?n=26&S=25&r=%5Etest.*');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('gerrit auth is used', () => {
|
test('gerrit auth is used', () => {
|
||||||
sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve());
|
sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve());
|
||||||
element._fetchJSON({url: 'foo'});
|
element._restApiHelper.fetchJSON({url: 'foo'});
|
||||||
assert(Gerrit.Auth.fetch.called);
|
assert(Gerrit.Auth.fetch.called);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getSuggestedAccounts does not return _fetchJSON', () => {
|
test('getSuggestedAccounts does not return _fetchJSON', () => {
|
||||||
const _fetchJSONSpy = sandbox.spy(element, '_fetchJSON');
|
const _fetchJSONSpy = sandbox.spy(element._restApiHelper, 'fetchJSON');
|
||||||
return element.getSuggestedAccounts().then(accts => {
|
return element.getSuggestedAccounts().then(accts => {
|
||||||
assert.isFalse(_fetchJSONSpy.called);
|
assert.isFalse(_fetchJSONSpy.called);
|
||||||
assert.equal(accts.length, 0);
|
assert.equal(accts.length, 0);
|
||||||
@@ -1157,7 +1058,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('_fetchJSON gets called by getSuggestedAccounts', () => {
|
test('_fetchJSON gets called by getSuggestedAccounts', () => {
|
||||||
const _fetchJSONStub = sandbox.stub(element, '_fetchJSON',
|
const _fetchJSONStub = sandbox.stub(element._restApiHelper, 'fetchJSON',
|
||||||
() => Promise.resolve());
|
() => Promise.resolve());
|
||||||
return element.getSuggestedAccounts('own').then(() => {
|
return element.getSuggestedAccounts('own').then(() => {
|
||||||
assert.deepEqual(_fetchJSONStub.lastCall.args[0].params, {
|
assert.deepEqual(_fetchJSONStub.lastCall.args[0].params, {
|
||||||
@@ -1229,7 +1130,7 @@ limitations under the License.
|
|||||||
const errFn = sinon.stub();
|
const errFn = sinon.stub();
|
||||||
sandbox.stub(element, 'getChangeActionURL')
|
sandbox.stub(element, 'getChangeActionURL')
|
||||||
.returns(Promise.resolve(''));
|
.returns(Promise.resolve(''));
|
||||||
sandbox.stub(element, '_fetchRawJSON')
|
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||||
.returns(Promise.resolve({ok: false, status: 500}));
|
.returns(Promise.resolve({ok: false, status: 500}));
|
||||||
return element._getChangeDetail(123, '516714', errFn).then(() => {
|
return element._getChangeDetail(123, '516714', errFn).then(() => {
|
||||||
assert.isTrue(errFn.called);
|
assert.isTrue(errFn.called);
|
||||||
@@ -1249,11 +1150,12 @@ limitations under the License.
|
|||||||
test('_getChangeDetail populates _projectLookup', () => {
|
test('_getChangeDetail populates _projectLookup', () => {
|
||||||
sandbox.stub(element, 'getChangeActionURL')
|
sandbox.stub(element, 'getChangeActionURL')
|
||||||
.returns(Promise.resolve(''));
|
.returns(Promise.resolve(''));
|
||||||
sandbox.stub(element, '_fetchRawJSON')
|
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||||
.returns(Promise.resolve({ok: true}));
|
.returns(Promise.resolve({ok: true}));
|
||||||
|
|
||||||
const mockResponse = {_number: 1, project: 'test'};
|
const mockResponse = {_number: 1, project: 'test'};
|
||||||
sandbox.stub(element, '_readResponsePayload').returns(Promise.resolve({
|
sandbox.stub(element._restApiHelper, 'readResponsePayload')
|
||||||
|
.returns(Promise.resolve({
|
||||||
parsed: mockResponse,
|
parsed: mockResponse,
|
||||||
raw: JSON.stringify(mockResponse),
|
raw: JSON.stringify(mockResponse),
|
||||||
}));
|
}));
|
||||||
@@ -1274,7 +1176,8 @@ limitations under the License.
|
|||||||
const mockResponse = {foo: 'bar', baz: 42};
|
const mockResponse = {foo: 'bar', baz: 42};
|
||||||
mockResponseSerial = element.JSON_PREFIX +
|
mockResponseSerial = element.JSON_PREFIX +
|
||||||
JSON.stringify(mockResponse);
|
JSON.stringify(mockResponse);
|
||||||
sandbox.stub(element, '_urlWithParams').returns(requestUrl);
|
sandbox.stub(element._restApiHelper, 'urlWithParams')
|
||||||
|
.returns(requestUrl);
|
||||||
sandbox.stub(element, 'getChangeActionURL')
|
sandbox.stub(element, 'getChangeActionURL')
|
||||||
.returns(Promise.resolve(requestUrl));
|
.returns(Promise.resolve(requestUrl));
|
||||||
collectSpy = sandbox.spy(element._etags, 'collect');
|
collectSpy = sandbox.spy(element._etags, 'collect');
|
||||||
@@ -1282,7 +1185,8 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('contributes to cache', () => {
|
test('contributes to cache', () => {
|
||||||
sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
|
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||||
|
.returns(Promise.resolve({
|
||||||
text: () => Promise.resolve(mockResponseSerial),
|
text: () => Promise.resolve(mockResponseSerial),
|
||||||
status: 200,
|
status: 200,
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -1297,7 +1201,8 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('uses cache on HTTP 304', () => {
|
test('uses cache on HTTP 304', () => {
|
||||||
sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
|
sandbox.stub(element._restApiHelper, 'fetchRawJSON')
|
||||||
|
.returns(Promise.resolve({
|
||||||
text: () => Promise.resolve(mockResponseSerial),
|
text: () => Promise.resolve(mockResponseSerial),
|
||||||
status: 304,
|
status: 304,
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -1346,7 +1251,7 @@ limitations under the License.
|
|||||||
|
|
||||||
suite('getChanges populates _projectLookup', () => {
|
suite('getChanges populates _projectLookup', () => {
|
||||||
test('multiple queries', () => {
|
test('multiple queries', () => {
|
||||||
sandbox.stub(element, '_fetchJSON')
|
sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||||
.returns(Promise.resolve([
|
.returns(Promise.resolve([
|
||||||
[
|
[
|
||||||
{_number: 1, project: 'test'},
|
{_number: 1, project: 'test'},
|
||||||
@@ -1366,7 +1271,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('no query', () => {
|
test('no query', () => {
|
||||||
sandbox.stub(element, '_fetchJSON')
|
sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||||
.returns(Promise.resolve([
|
.returns(Promise.resolve([
|
||||||
{_number: 1, project: 'test'},
|
{_number: 1, project: 'test'},
|
||||||
{_number: 2, project: 'test'},
|
{_number: 2, project: 'test'},
|
||||||
@@ -1386,7 +1291,7 @@ limitations under the License.
|
|||||||
|
|
||||||
test('_getChangeURLAndFetch', () => {
|
test('_getChangeURLAndFetch', () => {
|
||||||
element._projectLookup = {1: 'test'};
|
element._projectLookup = {1: 'test'};
|
||||||
const fetchStub = sandbox.stub(element, '_fetchJSON')
|
const fetchStub = sandbox.stub(element._restApiHelper, 'fetchJSON')
|
||||||
.returns(Promise.resolve());
|
.returns(Promise.resolve());
|
||||||
const req = {changeNum: 1, endpoint: '/test', patchNum: 1};
|
const req = {changeNum: 1, endpoint: '/test', patchNum: 1};
|
||||||
return element._getChangeURLAndFetch(req).then(() => {
|
return element._getChangeURLAndFetch(req).then(() => {
|
||||||
@@ -1397,7 +1302,7 @@ limitations under the License.
|
|||||||
|
|
||||||
test('_getChangeURLAndSend', () => {
|
test('_getChangeURLAndSend', () => {
|
||||||
element._projectLookup = {1: 'test'};
|
element._projectLookup = {1: 'test'};
|
||||||
const sendStub = sandbox.stub(element, '_send')
|
const sendStub = sandbox.stub(element._restApiHelper, 'send')
|
||||||
.returns(Promise.resolve());
|
.returns(Promise.resolve());
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
@@ -1419,7 +1324,8 @@ limitations under the License.
|
|||||||
const mockObject = {foo: 'bar', baz: 'foo'};
|
const mockObject = {foo: 'bar', baz: 'foo'};
|
||||||
const serial = element.JSON_PREFIX + JSON.stringify(mockObject);
|
const serial = element.JSON_PREFIX + JSON.stringify(mockObject);
|
||||||
const mockResponse = {text: () => Promise.resolve(serial)};
|
const mockResponse = {text: () => Promise.resolve(serial)};
|
||||||
return element._readResponsePayload(mockResponse).then(payload => {
|
return element._restApiHelper.readResponsePayload(mockResponse)
|
||||||
|
.then(payload => {
|
||||||
assert.deepEqual(payload.parsed, mockObject);
|
assert.deepEqual(payload.parsed, mockObject);
|
||||||
assert.equal(payload.raw, serial);
|
assert.equal(payload.raw, serial);
|
||||||
});
|
});
|
||||||
@@ -1428,7 +1334,7 @@ limitations under the License.
|
|||||||
test('_parsePrefixedJSON', () => {
|
test('_parsePrefixedJSON', () => {
|
||||||
const obj = {x: 3, y: {z: 4}, w: 23};
|
const obj = {x: 3, y: {z: 4}, w: 23};
|
||||||
const serial = element.JSON_PREFIX + JSON.stringify(obj);
|
const serial = element.JSON_PREFIX + JSON.stringify(obj);
|
||||||
const result = element._parsePrefixedJSON(serial);
|
const result = element._restApiHelper.parsePrefixedJSON(serial);
|
||||||
assert.deepEqual(result, obj);
|
assert.deepEqual(result, obj);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1450,7 +1356,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('generateAccountHttpPassword', () => {
|
test('generateAccountHttpPassword', () => {
|
||||||
const sendSpy = sandbox.spy(element, '_send');
|
const sendSpy = sandbox.spy(element._restApiHelper, 'send');
|
||||||
return element.generateAccountHttpPassword().then(() => {
|
return element.generateAccountHttpPassword().then(() => {
|
||||||
assert.isTrue(sendSpy.calledOnce);
|
assert.isTrue(sendSpy.calledOnce);
|
||||||
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
|
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
|
||||||
@@ -1535,11 +1441,12 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getDashboard', () => {
|
test('getDashboard', () => {
|
||||||
const fetchStub = sandbox.stub(element, '_fetchSharedCacheURL');
|
const fetchCacheURLStub = sandbox.stub(element._restApiHelper,
|
||||||
|
'fetchCacheURL');
|
||||||
element.getDashboard('gerrit/project', 'default:main');
|
element.getDashboard('gerrit/project', 'default:main');
|
||||||
assert.isTrue(fetchStub.calledOnce);
|
assert.isTrue(fetchCacheURLStub.calledOnce);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
fetchStub.lastCall.args[0].url,
|
fetchCacheURLStub.lastCall.args[0].url,
|
||||||
'/projects/gerrit%2Fproject/dashboards/default%3Amain');
|
'/projects/gerrit%2Fproject/dashboards/default%3Amain');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1607,7 +1514,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('_fetch forwards request and logs', () => {
|
test('_fetch forwards request and logs', () => {
|
||||||
const logStub = sandbox.stub(element, '_logCall');
|
const logStub = sandbox.stub(element._restApiHelper, '_logCall');
|
||||||
const response = {status: 404, text: sinon.stub()};
|
const response = {status: 404, text: sinon.stub()};
|
||||||
const url = 'my url';
|
const url = 'my url';
|
||||||
const fetchOptions = {method: 'DELETE'};
|
const fetchOptions = {method: 'DELETE'};
|
||||||
@@ -1615,7 +1522,7 @@ limitations under the License.
|
|||||||
const startTime = 123;
|
const startTime = 123;
|
||||||
sandbox.stub(Date, 'now').returns(startTime);
|
sandbox.stub(Date, 'now').returns(startTime);
|
||||||
const req = {url, fetchOptions};
|
const req = {url, fetchOptions};
|
||||||
return element._fetch(req).then(() => {
|
return element._restApiHelper.fetch(req).then(() => {
|
||||||
assert.isTrue(logStub.calledOnce);
|
assert.isTrue(logStub.calledOnce);
|
||||||
assert.isTrue(logStub.calledWith(req, startTime, response.status));
|
assert.isTrue(logStub.calledWith(req, startTime, response.status));
|
||||||
assert.isFalse(response.text.called);
|
assert.isFalse(response.text.called);
|
||||||
@@ -1627,10 +1534,11 @@ limitations under the License.
|
|||||||
const handler = sinon.stub();
|
const handler = sinon.stub();
|
||||||
element.addEventListener('rpc-log', handler);
|
element.addEventListener('rpc-log', handler);
|
||||||
|
|
||||||
element._logCall({url: 'url'}, 100, 200);
|
element._restApiHelper._logCall({url: 'url'}, 100, 200);
|
||||||
assert.isFalse(handler.called);
|
assert.isFalse(handler.called);
|
||||||
|
|
||||||
element._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
|
element._restApiHelper
|
||||||
|
._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
|
||||||
flushAsynchronousOperations();
|
flushAsynchronousOperations();
|
||||||
assert.isTrue(handler.calledOnce);
|
assert.isTrue(handler.calledOnce);
|
||||||
});
|
});
|
||||||
@@ -1639,7 +1547,7 @@ limitations under the License.
|
|||||||
sandbox.stub(element, 'getFromProjectLookup')
|
sandbox.stub(element, 'getFromProjectLookup')
|
||||||
.returns(Promise.resolve('test'));
|
.returns(Promise.resolve('test'));
|
||||||
const sendStub =
|
const sendStub =
|
||||||
sandbox.stub(element, '_send').returns(Promise.resolve());
|
sandbox.stub(element._restApiHelper, 'send').returns(Promise.resolve());
|
||||||
|
|
||||||
await element.saveChangeStarred(123, true);
|
await element.saveChangeStarred(123, true);
|
||||||
assert.isTrue(sendStub.calledOnce);
|
assert.isTrue(sendStub.calledOnce);
|
||||||
|
|||||||
@@ -0,0 +1,456 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<!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,6 +35,9 @@ const EXTERN_NAMES = [
|
|||||||
'GrReviewerUpdatesParser',
|
'GrReviewerUpdatesParser',
|
||||||
'GrCountStringFormatter',
|
'GrCountStringFormatter',
|
||||||
'GrThemeApi',
|
'GrThemeApi',
|
||||||
|
'SiteBasedCache',
|
||||||
|
'FetchPromisesCache',
|
||||||
|
'GrRestApiHelper',
|
||||||
'moment',
|
'moment',
|
||||||
'page',
|
'page',
|
||||||
'util',
|
'util',
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ limitations under the License.
|
|||||||
'shared/gr-rest-api-interface/gr-auth_test.html',
|
'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-rest-api-interface_test.html',
|
||||||
'shared/gr-rest-api-interface/gr-reviewer-updates-parser_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-select/gr-select_test.html',
|
||||||
'shared/gr-storage/gr-storage_test.html',
|
'shared/gr-storage/gr-storage_test.html',
|
||||||
'shared/gr-textarea/gr-textarea_test.html',
|
'shared/gr-textarea/gr-textarea_test.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user