blob: 7abcb0d6d68a378e221b873df1e45780072c9584 [file] [log] [blame]
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../test/common-test-setup';
import {
addListenerForTest,
assertFails,
MockPromise,
mockPromise,
waitEventLoop,
} from '../../test/test-utils';
import {GrReviewerUpdatesParser} from '../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
import {
ListChangesOption,
listChangesOptionsToHex,
} from '../../utils/change-util';
import {
createAccountDetailWithId,
createChange,
createComment,
createGerritInfo,
createParsedChange,
createServerInfo,
} from '../../test/test-data-generators';
import {CURRENT} from '../../utils/patch-set-util';
import {
parsePrefixedJSON,
readResponsePayload,
JSON_PREFIX,
} from '../../elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
import {GrRestApiServiceImpl} from './gr-rest-api-impl';
import {
CommentSide,
createDefaultEditPrefs,
HttpMethod,
} from '../../constants/constants';
import {
BasePatchSetNum,
ChangeInfo,
ChangeMessageId,
CommentInfo,
DashboardId,
DiffPreferenceInput,
EDIT,
EditPreferencesInfo,
Hashtag,
HashtagsInput,
NumericChangeId,
PARENT,
ParsedJSON,
PatchSetNum,
PreferencesInfo,
RepoName,
RevisionId,
RevisionPatchSetNum,
RobotCommentInfo,
ServerInfo,
Timestamp,
UrlEncodedCommentId,
} from '../../types/common';
import {assert} from '@open-wc/testing';
import {AuthService} from '../gr-auth/gr-auth';
import {GrAuthMock} from '../gr-auth/gr-auth_mock';
import {getBaseUrl} from '../../utils/url-util';
const EXPECTED_QUERY_OPTIONS = listChangesOptionsToHex(
ListChangesOption.CHANGE_ACTIONS,
// Current actions can be costly to calculate (e.g submit action)
// They are not used in bulk actions.
// ListChangesOption.CURRENT_ACTIONS,
ListChangesOption.CURRENT_REVISION,
ListChangesOption.DETAILED_LABELS,
ListChangesOption.SUBMIT_REQUIREMENTS
);
suite('gr-rest-api-service-impl tests', () => {
let element: GrRestApiServiceImpl;
let authService: AuthService;
let ctr = 0;
let originalCanonicalPath: string | undefined;
setup(() => {
// Modify CANONICAL_PATH to effectively reset cache.
ctr += 1;
originalCanonicalPath = window.CANONICAL_PATH;
window.CANONICAL_PATH = `test${ctr}`;
const testJSON = ')]}\'\n{"hello": "bonjour"}';
sinon.stub(window, 'fetch').resolves(new Response(testJSON));
// fake auth
authService = new GrAuthMock();
sinon.stub(authService, 'authCheck').resolves(true);
element = new GrRestApiServiceImpl(authService);
element._projectLookup = {};
});
teardown(() => {
window.CANONICAL_PATH = originalCanonicalPath;
});
test('parent diff comments are properly grouped', async () => {
sinon.stub(element._restApiHelper, 'fetchJSON').resolves({
'/COMMIT_MSG': [],
'sieve.go': [
{
updated: '2017-02-03 22:32:28.000000000',
message: 'this isn’t quite right',
},
{
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
updated: '2017-02-03 22:33:28.000000000',
},
],
} as unknown as ParsedJSON);
const obj = await element._getDiffComments(
42 as NumericChangeId,
'/comments',
undefined,
PARENT,
1 as PatchSetNum,
'sieve.go'
);
assert.equal(obj.baseComments.length, 1);
assert.deepEqual(obj.baseComments[0], {
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
path: 'sieve.go',
updated: '2017-02-03 22:33:28.000000000' as Timestamp,
} as RobotCommentInfo);
assert.equal(obj.comments.length, 1);
assert.deepEqual(obj.comments[0], {
message: 'this isn’t quite right',
path: 'sieve.go',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
} as RobotCommentInfo);
});
test('_setRange', () => {
const comments: CommentInfo[] = [
{
id: '1' as UrlEncodedCommentId,
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
},
{
id: '2' as UrlEncodedCommentId,
in_reply_to: '1' as UrlEncodedCommentId,
message: 'this isn’t quite right',
updated: '2017-02-03 22:33:28.000000000' as Timestamp,
},
];
const expectedResult: CommentInfo = {
id: '2' as UrlEncodedCommentId,
in_reply_to: '1' as UrlEncodedCommentId,
message: 'this isn’t quite right',
updated: '2017-02-03 22:33:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
};
const comment = comments[1];
assert.deepEqual(element._setRange(comments, comment), expectedResult);
});
test('_setRanges', () => {
const comments: CommentInfo[] = [
{
id: '3' as UrlEncodedCommentId,
in_reply_to: '2' as UrlEncodedCommentId,
message: 'this isn’t quite right either',
updated: '2017-02-03 22:34:28.000000000' as Timestamp,
},
{
id: '2' as UrlEncodedCommentId,
in_reply_to: '1' as UrlEncodedCommentId,
message: 'this isn’t quite right',
updated: '2017-02-03 22:33:28.000000000' as Timestamp,
},
{
id: '1' as UrlEncodedCommentId,
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
},
];
const expectedResult: CommentInfo[] = [
{
id: '1' as UrlEncodedCommentId,
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
},
{
id: '2' as UrlEncodedCommentId,
in_reply_to: '1' as UrlEncodedCommentId,
message: 'this isn’t quite right',
updated: '2017-02-03 22:33:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
},
{
id: '3' as UrlEncodedCommentId,
in_reply_to: '2' as UrlEncodedCommentId,
message: 'this isn’t quite right either',
updated: '2017-02-03 22:34:28.000000000' as Timestamp,
range: {
start_line: 1,
start_character: 1,
end_line: 2,
end_character: 1,
},
},
];
assert.deepEqual(element._setRanges(comments), expectedResult);
});
test('differing patch diff comments are properly grouped', async () => {
sinon.stub(element, 'getFromProjectLookup').resolves('test' as RepoName);
sinon.stub(element._restApiHelper, 'fetchJSON').callsFake(async request => {
const url = request.url;
if (url === '/changes/test~42/revisions/1/comments') {
return {
'/COMMIT_MSG': [],
'sieve.go': [
{
message: 'this isn’t quite right',
updated: '2017-02-03 22:32:28.000000000',
},
{
side: CommentSide.PARENT,
message: 'how did this work in the first place?',
updated: '2017-02-03 22:33:28.000000000',
},
],
} as unknown as ParsedJSON;
} else if (url === '/changes/test~42/revisions/2/comments') {
return {
'/COMMIT_MSG': [],
'sieve.go': [
{
message: 'What on earth are you thinking, here?',
updated: '2017-02-03 22:32:28.000000000',
},
{
side: CommentSide.PARENT,
message: 'Yeah not sure how this worked either?',
updated: '2017-02-03 22:33:28.000000000',
},
{
message: '¯\\_(ツ)_/¯',
updated: '2017-02-04 22:33:28.000000000',
},
],
} as unknown as ParsedJSON;
}
return undefined;
});
const obj = await element._getDiffComments(
42 as NumericChangeId,
'/comments',
undefined,
1 as BasePatchSetNum,
2 as PatchSetNum,
'sieve.go'
);
assert.equal(obj.baseComments.length, 1);
assert.deepEqual(obj.baseComments[0], {
message: 'this isn’t quite right',
path: 'sieve.go',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
} as RobotCommentInfo);
assert.equal(obj.comments.length, 2);
assert.deepEqual(obj.comments[0], {
message: 'What on earth are you thinking, here?',
path: 'sieve.go',
updated: '2017-02-03 22:32:28.000000000' as Timestamp,
} as RobotCommentInfo);
assert.deepEqual(obj.comments[1], {
message: '¯\\_(ツ)_/¯',
path: 'sieve.go',
updated: '2017-02-04 22:33:28.000000000' as Timestamp,
} as RobotCommentInfo);
});
test('server error', async () => {
const getResponseObjectStub = sinon.stub(element, 'getResponseObject');
sinon
.stub(authService, 'fetch')
.resolves(new Response(undefined, {status: 502}));
const serverErrorEventPromise = new Promise(resolve => {
addListenerForTest(document, 'server-error', resolve);
});
const response = await element._restApiHelper.fetchJSON({url: ''});
assert.isUndefined(response);
assert.isTrue(getResponseObjectStub.notCalled);
await serverErrorEventPromise;
});
test('legacy n,z key in change url is replaced', async () => {
const stub = sinon
.stub(element._restApiHelper, 'fetchJSON')
.resolves([] as unknown as ParsedJSON);
await element.getChanges(1, undefined, 'n,z');
assert.equal(stub.lastCall.args[0].params!.S, 0);
});
test('saveDiffPreferences invalidates cache line', () => {
const cacheKey = '/accounts/self/preferences.diff';
const sendStub = sinon.stub(element._restApiHelper, 'send');
element._cache.set(cacheKey, {tab_size: 4} as unknown as ParsedJSON);
element.saveDiffPreferences({
tab_size: 8,
ignore_whitespace: 'IGNORE_NONE',
});
assert.isTrue(sendStub.called);
assert.isFalse(element._cache.has(cacheKey));
});
suite('getAccountSuggestions', () => {
let fetchStub: sinon.SinonStub;
setup(() => {
fetchStub = sinon
.stub(element._restApiHelper, 'fetch')
.resolves(new Response());
});
test('url with just email', () => {
element.getSuggestedAccounts('bro');
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
`${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22`
);
});
test('url with email and canSee changeId', () => {
element.getSuggestedAccounts('bro', undefined, 341682 as NumericChangeId);
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
`${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22%20and%20cansee%3A341682`
);
});
test('url with email and canSee changeId and isActive', () => {
element.getSuggestedAccounts(
'bro',
undefined,
341682 as NumericChangeId,
true
);
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
`${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22%20and%20cansee%3A341682%20and%20is%3Aactive`
);
});
});
test('getAccount when resp is undefined clears cache', async () => {
const cacheKey = '/accounts/self/detail';
const account = createAccountDetailWithId();
element._cache.set(cacheKey, account);
const stub = sinon
.stub(element._restApiHelper, 'fetchCacheURL')
.callsFake(async req => {
req.errFn!(undefined);
return undefined;
});
assert.isTrue(element._cache.has(cacheKey));
await element.getAccount();
assert.isTrue(stub.called);
assert.isFalse(element._cache.has(cacheKey));
});
test('getAccount when status is 403 clears cache', async () => {
const cacheKey = '/accounts/self/detail';
const account = createAccountDetailWithId();
element._cache.set(cacheKey, account);
const stub = sinon
.stub(element._restApiHelper, 'fetchCacheURL')
.callsFake(async req => {
req.errFn!(new Response(undefined, {status: 403}));
return undefined;
});
assert.isTrue(element._cache.has(cacheKey));
await element.getAccount();
assert.isTrue(stub.called);
assert.isFalse(element._cache.has(cacheKey));
});
test('getAccount when resp is successful updates cache', async () => {
const cacheKey = '/accounts/self/detail';
const account = createAccountDetailWithId();
const stub = sinon
.stub(element._restApiHelper, 'fetchCacheURL')
.callsFake(async () => {
element._cache.set(cacheKey, account);
return undefined;
});
assert.isFalse(element._cache.has(cacheKey));
await element.getAccount();
assert.isTrue(stub.called);
assert.equal(element._cache.get(cacheKey), account);
});
const preferenceSetup = function (testJSON: unknown, loggedIn: boolean) {
sinon
.stub(element, 'getLoggedIn')
.callsFake(() => Promise.resolve(loggedIn));
sinon
.stub(element._restApiHelper, 'fetchCacheURL')
.callsFake(() => Promise.resolve(testJSON as ParsedJSON));
};
test('getPreferences returns correctly logged in', async () => {
const testJSON = {diff_view: 'SIDE_BY_SIDE'};
const loggedIn = true;
preferenceSetup(testJSON, loggedIn);
const obj = await element.getPreferences();
assert.equal(obj!.diff_view, 'SIDE_BY_SIDE');
});
test('getPreferences returns correctly on larger screens logged in', async () => {
const testJSON = {diff_view: 'UNIFIED_DIFF'};
const loggedIn = true;
preferenceSetup(testJSON, loggedIn);
const obj = await element.getPreferences();
assert.equal(obj!.diff_view, 'UNIFIED_DIFF');
});
test('getPreferences returns correctly on larger screens no login', async () => {
const testJSON = {diff_view: 'UNIFIED_DIFF'};
const loggedIn = false;
preferenceSetup(testJSON, loggedIn);
const obj = await element.getPreferences();
assert.equal(obj!.diff_view, 'SIDE_BY_SIDE');
});
test('savPreferences normalizes download scheme', () => {
const sendStub = sinon
.stub(element._restApiHelper, 'send')
.resolves(new Response());
element.savePreferences({download_scheme: 'HTTP'});
assert.isTrue(sendStub.called);
assert.equal(
(sendStub.lastCall.args[0].body as Partial<PreferencesInfo>)
.download_scheme,
'http'
);
});
test('getDiffPreferences returns correct defaults', async () => {
sinon.stub(element, 'getLoggedIn').callsFake(() => Promise.resolve(false));
const obj = (await element.getDiffPreferences())!;
assert.equal(obj.context, 10);
assert.equal(obj.cursor_blink_rate, 0);
assert.equal(obj.font_size, 12);
assert.equal(obj.ignore_whitespace, 'IGNORE_NONE');
assert.equal(obj.line_length, 100);
assert.equal(obj.line_wrapping, false);
assert.equal(obj.show_line_endings, true);
assert.equal(obj.show_tabs, true);
assert.equal(obj.show_whitespace_errors, true);
assert.equal(obj.syntax_highlighting, true);
assert.equal(obj.tab_size, 8);
});
test('saveDiffPreferences set show_tabs to false', () => {
const sendStub = sinon.stub(element._restApiHelper, 'send');
element.saveDiffPreferences({
show_tabs: false,
ignore_whitespace: 'IGNORE_NONE',
});
assert.isTrue(sendStub.called);
assert.equal(
(sendStub.lastCall.args[0].body as Partial<DiffPreferenceInput>)
.show_tabs,
false
);
});
test('getEditPreferences returns correct defaults', async () => {
sinon.stub(element, 'getLoggedIn').callsFake(() => Promise.resolve(false));
const obj = (await element.getEditPreferences())!;
assert.equal(obj.auto_close_brackets, false);
assert.equal(obj.cursor_blink_rate, 0);
assert.equal(obj.hide_line_numbers, false);
assert.equal(obj.hide_top_menu, false);
assert.equal(obj.indent_unit, 2);
assert.equal(obj.indent_with_tabs, false);
assert.equal(obj.key_map_type, 'DEFAULT');
assert.equal(obj.line_length, 100);
assert.equal(obj.line_wrapping, false);
assert.equal(obj.match_brackets, true);
assert.equal(obj.show_base, false);
assert.equal(obj.show_tabs, true);
assert.equal(obj.show_whitespace_errors, true);
assert.equal(obj.syntax_highlighting, true);
assert.equal(obj.tab_size, 8);
assert.equal(obj.theme, 'DEFAULT');
});
test('saveEditPreferences set show_tabs to false', () => {
const sendStub = sinon.stub(element._restApiHelper, 'send');
element.saveEditPreferences({
...createDefaultEditPrefs(),
show_tabs: false,
});
assert.isTrue(sendStub.called);
assert.equal(
(sendStub.lastCall.args[0].body as EditPreferencesInfo).show_tabs,
false
);
});
test('confirmEmail', () => {
const sendStub = sinon.spy(element._restApiHelper, 'send');
element.confirmEmail('foo');
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
assert.equal(sendStub.lastCall.args[0].url, '/config/server/email.confirm');
assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
});
test('setPreferredAccountEmail', async () => {
const email1 = 'email1@example.com';
const email2 = 'email2@example.com';
const encodedEmail = encodeURIComponent(email2);
const sendStub = sinon.stub(element._restApiHelper, 'send').resolves();
element._cache.set('/accounts/self/emails', [
{email: email1, preferred: true},
{email: email2, preferred: false},
]);
await element.setPreferredAccountEmail(email2);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
assert.equal(
sendStub.lastCall.args[0].url,
`/accounts/self/emails/${encodedEmail}/preferred`
);
assert.deepEqual(element._cache.get('/accounts/self/emails'), [
{email: email1, preferred: false},
{email: email2, preferred: true},
]);
});
test('setAccountStatus', async () => {
const sendStub = sinon
.stub(element._restApiHelper, 'send')
.resolves('OOO' as unknown as ParsedJSON);
element._cache.set('/accounts/self/detail', createAccountDetailWithId());
await element.setAccountStatus('OOO');
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
assert.equal(sendStub.lastCall.args[0].url, '/accounts/self/status');
assert.deepEqual(sendStub.lastCall.args[0].body, {status: 'OOO'});
assert.deepEqual(
element._cache.get('/accounts/self/detail')!.status,
'OOO'
);
});
suite('draft comments', () => {
test('_sendDiffDraftRequest pending requests tracked', async () => {
const obj = element._pendingRequests;
sinon
.stub(element, '_getChangeURLAndSend')
.callsFake(() => mockPromise());
assert.notOk(element.hasPendingDiffDrafts());
element._sendDiffDraftRequest(
HttpMethod.PUT,
123 as NumericChangeId,
1 as PatchSetNum,
{}
);
assert.equal(obj.sendDiffDraft.length, 1);
assert.isTrue(!!element.hasPendingDiffDrafts());
element._sendDiffDraftRequest(
HttpMethod.PUT,
123 as NumericChangeId,
1 as PatchSetNum,
{}
);
assert.equal(obj.sendDiffDraft.length, 2);
assert.isTrue(!!element.hasPendingDiffDrafts());
for (const promise of obj.sendDiffDraft) {
(promise as MockPromise<void>).resolve();
}
await element.awaitPendingDiffDrafts();
assert.equal(obj.sendDiffDraft.length, 0);
assert.isFalse(!!element.hasPendingDiffDrafts());
});
suite('_failForCreate200', () => {
test('_sendDiffDraftRequest checks for 200 on create', async () => {
const sendPromise = Promise.resolve({} as unknown as ParsedJSON);
sinon.stub(element, '_getChangeURLAndSend').returns(sendPromise);
const failStub = sinon.stub(element, '_failForCreate200').resolves();
await element._sendDiffDraftRequest(
HttpMethod.PUT,
123 as NumericChangeId,
4 as PatchSetNum,
{}
);
assert.isTrue(failStub.calledOnce);
assert.isTrue(failStub.calledWithExactly(sendPromise));
});
test('_sendDiffDraftRequest no checks for 200 on non create', async () => {
sinon.stub(element, '_getChangeURLAndSend').resolves();
const failStub = sinon.stub(element, '_failForCreate200').resolves();
await element._sendDiffDraftRequest(
HttpMethod.PUT,
123 as NumericChangeId,
4 as PatchSetNum,
{
id: '123' as UrlEncodedCommentId,
}
);
assert.isFalse(failStub.called);
});
test('_failForCreate200 fails on 200', async () => {
const result = new Response(undefined, {
status: 200,
headers: {
'Set-CoOkiE': 'secret',
Innocuous: 'hello',
},
});
const error = await assertFails<Error>(
element._failForCreate200(Promise.resolve(result))
);
assert.isOk(error);
assert.include(error.message, 'Saving draft resulted in HTTP 200');
assert.include(error.message, 'hello');
assert.notInclude(error.message, 'secret');
});
test('_failForCreate200 does not fail on 201', () => {
const result = new Response(undefined, {status: 201});
return element._failForCreate200(Promise.resolve(result));
});
});
});
test('saveChangeEdit', async () => {
element._projectLookup = {1: Promise.resolve('test' as RepoName)};
const change_num = 1 as NumericChangeId;
const file_name = 'index.php';
const file_contents = '<?php';
const sendStub = sinon
.stub(element._restApiHelper, 'send')
.resolves([
change_num,
file_name,
file_contents,
] as unknown as ParsedJSON);
sinon
.stub(element, 'getResponseObject')
.resolves([
change_num,
file_name,
file_contents,
] as unknown as ParsedJSON);
element._cache.set(
`/changes/${change_num}/edit/${file_name}`,
{} as unknown as ParsedJSON
);
await element.saveChangeEdit(change_num, file_name, file_contents);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
assert.equal(
sendStub.lastCall.args[0].url,
'/changes/test~1/edit/' + file_name
);
assert.equal(sendStub.lastCall.args[0].body, file_contents);
});
test('putChangeCommitMessage', async () => {
element._projectLookup = {1: Promise.resolve('test' as RepoName)};
const change_num = 1 as NumericChangeId;
const message = 'this is a commit message';
const sendStub = sinon
.stub(element._restApiHelper, 'send')
.resolves([change_num, message] as unknown as ParsedJSON);
sinon
.stub(element, 'getResponseObject')
.resolves([change_num, message] as unknown as ParsedJSON);
element._cache.set(
`/changes/${change_num}/message`,
{} as unknown as ParsedJSON
);
await element.putChangeCommitMessage(change_num, message);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.PUT);
assert.equal(sendStub.lastCall.args[0].url, '/changes/test~1/message');
assert.deepEqual(sendStub.lastCall.args[0].body, {
message,
});
});
test('deleteChangeCommitMessage', async () => {
element._projectLookup = {1: Promise.resolve('test' as RepoName)};
const change_num = 1 as NumericChangeId;
const messageId = 'abc' as ChangeMessageId;
const sendStub = sinon
.stub(element._restApiHelper, 'send')
.resolves([change_num, messageId] as unknown as ParsedJSON);
sinon
.stub(element, 'getResponseObject')
.resolves([change_num, messageId] as unknown as ParsedJSON);
await element.deleteChangeCommitMessage(change_num, messageId);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.DELETE);
assert.equal(sendStub.lastCall.args[0].url, '/changes/test~1/messages/abc');
});
test('startWorkInProgress', () => {
const sendStub = sinon
.stub(element, '_getChangeURLAndSend')
.resolves('ok' as unknown as ParsedJSON);
element.startWorkInProgress(42 as NumericChangeId);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].changeNum, 42 as NumericChangeId);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.POST);
assert.isNotOk(sendStub.lastCall.args[0].patchNum);
assert.equal(sendStub.lastCall.args[0].endpoint, '/wip');
assert.deepEqual(sendStub.lastCall.args[0].body, {});
element.startWorkInProgress(42 as NumericChangeId, 'revising...');
assert.isTrue(sendStub.calledTwice);
assert.equal(sendStub.lastCall.args[0].changeNum, 42 as NumericChangeId);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.POST);
assert.isNotOk(sendStub.lastCall.args[0].patchNum);
assert.equal(sendStub.lastCall.args[0].endpoint, '/wip');
assert.deepEqual(sendStub.lastCall.args[0].body, {
message: 'revising...',
});
});
test('deleteComment', async () => {
const comment = createComment();
const sendStub = sinon
.stub(element, '_getChangeURLAndSend')
.resolves(comment as unknown as ParsedJSON);
const response = await element.deleteComment(
123 as NumericChangeId,
1 as PatchSetNum,
'01234' as UrlEncodedCommentId,
'removal reason'
);
assert.equal(response, comment);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].changeNum, 123 as NumericChangeId);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.POST);
assert.equal(sendStub.lastCall.args[0].patchNum, 1 as PatchSetNum);
assert.equal(sendStub.lastCall.args[0].endpoint, '/comments/01234/delete');
assert.deepEqual(sendStub.lastCall.args[0].body, {
reason: 'removal reason',
});
});
test('createRepo encodes name', async () => {
const sendStub = sinon.stub(element._restApiHelper, 'send').resolves();
await element.createRepo({name: 'x/y' as RepoName});
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].url, '/projects/x%2Fy');
});
test('queryChangeFiles', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
await element.queryChangeFiles(42 as NumericChangeId, EDIT, 'test/path.js');
assert.equal(fetchStub.lastCall.args[0].changeNum, 42 as NumericChangeId);
assert.equal(
fetchStub.lastCall.args[0].endpoint,
'/files?q=test%2Fpath.js'
);
assert.equal(fetchStub.lastCall.args[0].revision, EDIT);
});
test('normal use', () => {
const defaultQuery = '';
assert.equal(
element._getReposUrl('test', 25).toString(),
[false, '/projects/?n=26&S=0&d=&m=test'].toString()
);
assert.equal(
element._getReposUrl(undefined, 25).toString(),
[false, `/projects/?n=26&S=0&d=&m=${defaultQuery}`].toString()
);
assert.equal(
element._getReposUrl('test', 25, 25).toString(),
[false, '/projects/?n=26&S=25&d=&m=test'].toString()
);
assert.equal(
element._getReposUrl('inname:test', 25, 25).toString(),
[true, '/projects/?n=26&S=25&query=inname%3Atest'].toString()
);
});
test('invalidateReposCache', () => {
const url = '/projects/?n=26&S=0&query=test';
element._cache.set(url, {} as unknown as ParsedJSON);
element.invalidateReposCache();
assert.isUndefined(element._sharedFetchPromises.get(url));
assert.isFalse(element._cache.has(url));
});
test('invalidateAccountsCache', () => {
const url = '/accounts/self/detail';
element._cache.set(url, {} as unknown as ParsedJSON);
element.invalidateAccountsCache();
assert.isUndefined(element._sharedFetchPromises.get(url));
assert.isFalse(element._cache.has(url));
});
suite('getRepos', () => {
const defaultQuery = '';
let fetchCacheURLStub: sinon.SinonStub;
setup(() => {
fetchCacheURLStub = sinon
.stub(element._restApiHelper, 'fetchCacheURL')
.resolves([] as unknown as ParsedJSON);
});
test('normal use', () => {
element.getRepos('test', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=test'
);
element.getRepos(undefined, 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
`/projects/?n=26&S=0&d=&m=${defaultQuery}`
);
element.getRepos('test', 25, 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=25&d=&m=test'
);
});
test('with blank', () => {
element.getRepos('test/test', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=test%2Ftest'
);
});
test('with hyphen', () => {
element.getRepos('foo-bar', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=foo-bar'
);
});
test('with leading hyphen', () => {
element.getRepos('-bar', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=-bar'
);
});
test('with trailing hyphen', () => {
element.getRepos('foo-bar-', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=foo-bar-'
);
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=foo_bar'
);
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=foo_bar'
);
});
test('hyphen only', () => {
element.getRepos('-', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&d=&m=-'
);
});
test('using query', () => {
element.getRepos('description:project', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=description%3Aproject'
);
});
});
test('_getGroupsUrl normal use', () => {
assert.equal(element._getGroupsUrl('test', 25), '/groups/?n=26&S=0&m=test');
assert.equal(element._getGroupsUrl('', 25), '/groups/?n=26&S=0');
assert.equal(
element._getGroupsUrl('test', 25, 25),
'/groups/?n=26&S=25&m=test'
);
});
test('invalidateGroupsCache', () => {
const url = '/groups/?n=26&S=0&m=test';
element._cache.set(url, {} as unknown as ParsedJSON);
element.invalidateGroupsCache();
assert.isUndefined(element._sharedFetchPromises.get(url));
assert.isFalse(element._cache.has(url));
});
suite('getGroups', () => {
let fetchCacheURLStub: sinon.SinonStub;
setup(() => {
fetchCacheURLStub = sinon.stub(element._restApiHelper, 'fetchCacheURL');
});
test('normal use', () => {
element.getGroups('test', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&m=test'
);
element.getGroups('', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url, '/groups/?n=26&S=0');
element.getGroups('test', 25, 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&m=test'
);
});
test('regex', () => {
element.getGroups('^test.*', 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&r=%5Etest.*'
);
element.getGroups('^test.*', 25, 25);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&r=%5Etest.*'
);
});
});
test('gerrit auth is used', () => {
const fetchStub = sinon.stub(authService, 'fetch').resolves();
element._restApiHelper.fetchJSON({url: 'foo'});
assert(fetchStub.called);
});
test('getSuggestedAccounts does not return fetchJSON', async () => {
const fetchJSONSpy = sinon.spy(element._restApiHelper, 'fetchJSON');
const accts = await element.getSuggestedAccounts('');
assert.isFalse(fetchJSONSpy.called);
assert.equal(accts!.length, 0);
});
test('fetchJSON gets called by getSuggestedAccounts', async () => {
const fetchJSONStub = sinon
.stub(element._restApiHelper, 'fetchJSON')
.resolves();
await element.getSuggestedAccounts('own');
assert.deepEqual(fetchJSONStub.lastCall.args[0].params, {
q: '"own"',
o: 'DETAILS',
});
});
suite('getChangeDetail', () => {
suite('change detail options', () => {
let changeDetailStub: sinon.SinonStub;
setup(() => {
changeDetailStub = sinon
.stub(element, '_getChangeDetail')
.resolves({...createChange(), _number: 123 as NumericChangeId});
});
test('signed pushes disabled', async () => {
sinon.stub(element, 'getConfig').resolves({
...createServerInfo(),
receive: {enable_signed_push: undefined},
});
const change = await element.getChangeDetail(123 as NumericChangeId);
assert.strictEqual(123, change!._number);
const options = changeDetailStub.firstCall.args[1];
assert.isNotOk(
parseInt(options, 16) & (1 << ListChangesOption.PUSH_CERTIFICATES)
);
});
test('signed pushes enabled', async () => {
sinon.stub(element, 'getConfig').resolves({
...createServerInfo(),
receive: {enable_signed_push: 'true'},
});
const change = await element.getChangeDetail(123 as NumericChangeId);
assert.strictEqual(123, change!._number);
const options = changeDetailStub.firstCall.args[1];
assert.ok(
parseInt(options, 16) & (1 << ListChangesOption.PUSH_CERTIFICATES)
);
});
});
test('GrReviewerUpdatesParser.parse is used', async () => {
const changeInfo = createParsedChange();
const parseStub = sinon
.stub(GrReviewerUpdatesParser, 'parse')
.resolves(changeInfo);
const result = await element.getChangeDetail(42 as NumericChangeId);
assert.isTrue(parseStub.calledOnce);
assert.equal(result, changeInfo);
});
test('_getChangeDetail passes params to ETags decorator', async () => {
const changeNum = 4321 as NumericChangeId;
element._projectLookup[changeNum] = Promise.resolve('test' as RepoName);
const expectedUrl = `${window.CANONICAL_PATH}/changes/test~4321/detail?O=516714`;
const optionsStub = sinon.stub(element._etags, 'getOptions');
const collectStub = sinon.stub(element._etags, 'collect');
await element._getChangeDetail(changeNum, '516714');
assert.isTrue(optionsStub.calledWithExactly(expectedUrl));
assert.equal(collectStub.lastCall.args[0], expectedUrl);
});
test('_getChangeDetail calls errFn on 500', async () => {
const errFn = sinon.stub();
sinon.stub(element, 'getChangeActionURL').resolves('');
sinon
.stub(element._restApiHelper, 'fetchRawJSON')
.resolves(new Response(undefined, {status: 500}));
await element._getChangeDetail(123 as NumericChangeId, '516714', errFn);
assert.isTrue(errFn.called);
});
test('_getChangeDetail populates _projectLookup', async () => {
sinon.stub(element, 'getChangeActionURL').resolves('');
sinon.stub(element._restApiHelper, 'fetchRawJSON').resolves(
new Response(')]}\'{"_number":1,"project":"test"}', {
status: 200,
})
);
await element._getChangeDetail(1 as NumericChangeId, '516714');
assert.equal(Object.keys(element._projectLookup).length, 1);
const project = await element._projectLookup[1];
assert.equal(project, 'test' as RepoName);
});
suite('_getChangeDetail ETag cache', () => {
let requestUrl: string;
let mockResponseSerial: string;
let collectSpy: sinon.SinonSpy;
setup(() => {
requestUrl = '/foo/bar';
const mockResponse = {foo: 'bar', baz: 42};
mockResponseSerial = JSON_PREFIX + JSON.stringify(mockResponse);
sinon.stub(element._restApiHelper, 'urlWithParams').returns(requestUrl);
sinon.stub(element, 'getChangeActionURL').resolves(requestUrl);
collectSpy = sinon.spy(element._etags, 'collect');
});
test('contributes to cache', async () => {
const getPayloadSpy = sinon.spy(element._etags, 'getCachedPayload');
sinon.stub(element._restApiHelper, 'fetchRawJSON').resolves(
new Response(mockResponseSerial, {
status: 200,
})
);
await element._getChangeDetail(123 as NumericChangeId, '516714');
assert.isFalse(getPayloadSpy.called);
assert.isTrue(collectSpy.calledOnce);
const cachedResponse = element._etags.getCachedPayload(requestUrl);
assert.equal(cachedResponse, mockResponseSerial);
});
test('uses cache on HTTP 304', async () => {
const getPayloadStub = sinon.stub(element._etags, 'getCachedPayload');
getPayloadStub.returns(mockResponseSerial);
sinon.stub(element._restApiHelper, 'fetchRawJSON').resolves(
new Response(undefined, {
status: 304,
})
);
await element._getChangeDetail(123 as NumericChangeId, '');
assert.isFalse(collectSpy.called);
assert.isTrue(getPayloadStub.calledOnce);
});
});
});
test('setInProjectLookup', async () => {
element.setInProjectLookup(555 as NumericChangeId, 'project' as RepoName);
const project = await element.getFromProjectLookup(555 as NumericChangeId);
assert.deepEqual(project, 'project' as RepoName);
});
suite('getFromProjectLookup', () => {
const changeNum = 555 as NumericChangeId;
const repo = 'test-repo' as RepoName;
test('getChange fails to yield a project', async () => {
const promise = mockPromise<null>();
sinon.stub(element, 'getChange').returns(promise);
const projectLookup = element.getFromProjectLookup(changeNum);
promise.resolve(null);
assert.isUndefined(await projectLookup);
});
test('getChange succeeds with project', async () => {
const promise = mockPromise<null | ChangeInfo>();
sinon.stub(element, 'getChange').returns(promise);
const projectLookup = element.getFromProjectLookup(changeNum);
promise.resolve({...createChange(), project: repo});
assert.equal(await projectLookup, repo);
assert.deepEqual(element._projectLookup, {'555': projectLookup});
});
test('getChange fails, but a setInProjectLookup() call is used as fallback', async () => {
const promise = mockPromise<null>();
sinon.stub(element, 'getChange').returns(promise);
const projectLookup = element.getFromProjectLookup(changeNum);
element.setInProjectLookup(changeNum, repo);
promise.resolve(null);
assert.equal(await projectLookup, repo);
});
});
suite('getChanges populates _projectLookup', () => {
test('multiple queries', async () => {
sinon.stub(element._restApiHelper, 'fetchJSON').resolves([
[
{_number: 1, project: 'test'},
{_number: 2, project: 'test'},
],
[{_number: 3, project: 'test/test'}],
] as unknown as ParsedJSON);
// When query instanceof Array, fetchJSON returns
// Array<Array<Object>>.
await element.getChangesForMultipleQueries(undefined, []);
assert.equal(Object.keys(element._projectLookup).length, 3);
const project1 = await element.getFromProjectLookup(1 as NumericChangeId);
assert.equal(project1, 'test' as RepoName);
const project2 = await element.getFromProjectLookup(2 as NumericChangeId);
assert.equal(project2, 'test' as RepoName);
const project3 = await element.getFromProjectLookup(3 as NumericChangeId);
assert.equal(project3, 'test/test' as RepoName);
});
test('no query', async () => {
sinon.stub(element._restApiHelper, 'fetchJSON').resolves([
{_number: 1, project: 'test'},
{_number: 2, project: 'test'},
{_number: 3, project: 'test/test'},
] as unknown as ParsedJSON);
// When query !instanceof Array, fetchJSON returns Array<Object>.
await element.getChanges();
assert.equal(Object.keys(element._projectLookup).length, 3);
const project1 = await element.getFromProjectLookup(1 as NumericChangeId);
assert.equal(project1, 'test' as RepoName);
const project2 = await element.getFromProjectLookup(2 as NumericChangeId);
assert.equal(project2, 'test' as RepoName);
const project3 = await element.getFromProjectLookup(3 as NumericChangeId);
assert.equal(project3, 'test/test' as RepoName);
});
});
test('getDetailedChangesWithActions', async () => {
const c1 = createChange();
c1._number = 1 as NumericChangeId;
const c2 = createChange();
c2._number = 2 as NumericChangeId;
const getChangesStub = sinon
.stub(element, 'getChanges')
.callsFake((changesPerPage, query, offset, options) => {
assert.isUndefined(changesPerPage);
assert.strictEqual(query, 'change:1 OR change:2');
assert.isUndefined(offset);
assert.strictEqual(options, EXPECTED_QUERY_OPTIONS);
return Promise.resolve([]);
});
await element.getDetailedChangesWithActions([c1._number, c2._number]);
assert.isTrue(getChangesStub.calledOnce);
});
test('_getChangeURLAndFetch', async () => {
element._projectLookup = {1: Promise.resolve('test' as RepoName)};
const fetchStub = sinon
.stub(element._restApiHelper, 'fetchJSON')
.resolves();
const req = {
changeNum: 1 as NumericChangeId,
endpoint: '/test',
revision: 1 as RevisionId,
};
await element._getChangeURLAndFetch(req);
assert.equal(
fetchStub.lastCall.args[0].url,
'/changes/test~1/revisions/1/test'
);
});
test('_getChangeURLAndSend', async () => {
element._projectLookup = {1: Promise.resolve('test' as RepoName)};
const sendStub = sinon.stub(element._restApiHelper, 'send').resolves();
const req = {
changeNum: 1 as NumericChangeId,
method: HttpMethod.POST,
patchNum: 1 as PatchSetNum,
endpoint: '/test',
};
await element._getChangeURLAndSend(req);
assert.isTrue(sendStub.calledOnce);
assert.equal(sendStub.lastCall.args[0].method, HttpMethod.POST);
assert.equal(
sendStub.lastCall.args[0].url,
'/changes/test~1/revisions/1/test'
);
});
suite('reading responses', () => {
test('_readResponsePayload', async () => {
const mockObject = {foo: 'bar', baz: 'foo'} as unknown as ParsedJSON;
const serial = JSON_PREFIX + JSON.stringify(mockObject);
const response = new Response(serial);
const payload = await readResponsePayload(response);
assert.deepEqual(payload.parsed, mockObject);
assert.equal(payload.raw, serial);
});
test('_parsePrefixedJSON', () => {
const obj = {x: 3, y: {z: 4}, w: 23} as unknown as ParsedJSON;
const serial = JSON_PREFIX + JSON.stringify(obj);
const result = parsePrefixedJSON(serial);
assert.deepEqual(result, obj);
});
});
test('setChangeTopic', async () => {
const sendSpy = sinon.spy(element, '_getChangeURLAndSend');
await element.setChangeTopic(123 as NumericChangeId, 'foo-bar');
assert.isTrue(sendSpy.calledOnce);
assert.deepEqual(sendSpy.lastCall.args[0].body, {topic: 'foo-bar'});
});
test('setChangeHashtag', async () => {
const sendSpy = sinon.spy(element, '_getChangeURLAndSend');
await element.setChangeHashtag(123 as NumericChangeId, {
add: ['foo-bar' as Hashtag],
});
assert.isTrue(sendSpy.calledOnce);
assert.sameDeepMembers(
(sendSpy.lastCall.args[0].body! as HashtagsInput).add!,
['foo-bar']
);
});
test('generateAccountHttpPassword', async () => {
const sendSpy = sinon.spy(element._restApiHelper, 'send');
await element.generateAccountHttpPassword();
assert.isTrue(sendSpy.calledOnce);
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
});
suite('getChangeFiles', () => {
test('patch only', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
const range = {basePatchNum: PARENT, patchNum: 2 as RevisionPatchSetNum};
await element.getChangeFiles(123 as NumericChangeId, range);
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.lastCall.args[0].revision,
2 as RevisionPatchSetNum
);
assert.isNotOk(fetchStub.lastCall.args[0].params);
});
test('simple range', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
const range = {
basePatchNum: 4 as BasePatchSetNum,
patchNum: 5 as RevisionPatchSetNum,
};
await element.getChangeFiles(123 as NumericChangeId, range);
assert.isTrue(fetchStub.calledOnce);
assert.equal(fetchStub.lastCall.args[0].revision, 5 as RevisionId);
assert.isOk(fetchStub.lastCall.args[0].params);
assert.equal(fetchStub.lastCall.args[0].params!.base, 4);
assert.isNotOk(fetchStub.lastCall.args[0].params!.parent);
});
test('parent index', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
const range = {
basePatchNum: -3 as BasePatchSetNum,
patchNum: 5 as RevisionPatchSetNum,
};
await element.getChangeFiles(123 as NumericChangeId, range);
assert.isTrue(fetchStub.calledOnce);
assert.equal(fetchStub.lastCall.args[0].revision, 5 as RevisionId);
assert.isOk(fetchStub.lastCall.args[0].params);
assert.isNotOk(fetchStub.lastCall.args[0].params!.base);
assert.equal(fetchStub.lastCall.args[0].params!.parent, 3);
});
});
suite('getDiff', () => {
test('patchOnly', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
await element.getDiff(
123 as NumericChangeId,
PARENT,
2 as PatchSetNum,
'foo/bar.baz'
);
assert.isTrue(fetchStub.calledOnce);
assert.equal(fetchStub.lastCall.args[0].revision, 2 as RevisionId);
assert.isOk(fetchStub.lastCall.args[0].params);
assert.isNotOk(fetchStub.lastCall.args[0].params!.parent);
assert.isNotOk(fetchStub.lastCall.args[0].params!.base);
});
test('simple range', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
await element.getDiff(
123 as NumericChangeId,
4 as PatchSetNum,
5 as PatchSetNum,
'foo/bar.baz'
);
assert.isTrue(fetchStub.calledOnce);
assert.equal(fetchStub.lastCall.args[0].revision, 5 as RevisionId);
assert.isOk(fetchStub.lastCall.args[0].params);
assert.isNotOk(fetchStub.lastCall.args[0].params!.parent);
assert.equal(fetchStub.lastCall.args[0].params!.base, 4);
});
test('parent index', async () => {
const fetchStub = sinon.stub(element, '_getChangeURLAndFetch').resolves();
await element.getDiff(
123 as NumericChangeId,
-3 as PatchSetNum,
5 as PatchSetNum,
'foo/bar.baz'
);
assert.isTrue(fetchStub.calledOnce);
assert.equal(fetchStub.lastCall.args[0].revision, 5 as RevisionId);
assert.isOk(fetchStub.lastCall.args[0].params);
assert.isNotOk(fetchStub.lastCall.args[0].params!.base);
assert.equal(fetchStub.lastCall.args[0].params!.parent, 3);
});
});
test('getDashboard', () => {
const fetchCacheURLStub = sinon.stub(
element._restApiHelper,
'fetchCacheURL'
);
element.getDashboard(
'gerrit/project' as RepoName,
'default:main' as DashboardId
);
assert.isTrue(fetchCacheURLStub.calledOnce);
assert.equal(
fetchCacheURLStub.lastCall.args[0].url,
'/projects/gerrit%2Fproject/dashboards/default%3Amain'
);
});
test('getFileContent', async () => {
sinon.stub(element, '_getChangeURLAndSend').resolves(
new Response(undefined, {
status: 200,
headers: {
'X-FYI-Content-Type': 'text/java',
},
}) as unknown as ParsedJSON
);
sinon
.stub(element, 'getResponseObject')
.resolves('new content' as unknown as ParsedJSON);
const edit = await element.getFileContent(
1 as NumericChangeId,
'tst/path',
'EDIT' as PatchSetNum
);
assert.deepEqual(edit, {
content: 'new content',
type: 'text/java',
ok: true,
});
const normal = await element.getFileContent(
1 as NumericChangeId,
'tst/path',
'3' as PatchSetNum
);
assert.deepEqual(normal, {
content: 'new content',
type: 'text/java',
ok: true,
});
});
test('getFileContent suppresses 404s', async () => {
const res404 = new Response(undefined, {status: 404});
const res500 = new Response(undefined, {status: 500});
const spy = sinon.spy();
addListenerForTest(document, 'server-error', spy);
const authStub = sinon.stub(authService, 'fetch').resolves(res404);
sinon.stub(element, '_changeBaseURL').resolves('');
await element.getFileContent(
1 as NumericChangeId,
'tst/path',
1 as PatchSetNum
);
await waitEventLoop();
assert.isFalse(spy.called);
authStub.reset();
authStub.resolves(res500);
await element.getFileContent(
1 as NumericChangeId,
'tst/path',
1 as PatchSetNum
);
assert.isTrue(spy.called);
assert.notEqual(spy.lastCall.args[0].detail.response.status, 404);
});
test('getChangeFilesOrEditFiles is edit-sensitive', async () => {
const getChangeFilesStub = sinon
.stub(element, 'getChangeFiles')
.resolves({});
const getChangeEditFilesStub = sinon
.stub(element, 'getChangeEditFiles')
.resolves({files: {}});
await element.getChangeOrEditFiles(1 as NumericChangeId, {
basePatchNum: PARENT,
patchNum: EDIT,
});
assert.isTrue(getChangeEditFilesStub.calledOnce);
assert.isFalse(getChangeFilesStub.called);
await element.getChangeOrEditFiles(1 as NumericChangeId, {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
});
assert.isTrue(getChangeEditFilesStub.calledOnce);
assert.isTrue(getChangeFilesStub.calledOnce);
});
test('_fetch forwards request and logs', async () => {
const logStub = sinon.stub(element._restApiHelper, '_logCall');
const response = new Response(undefined, {status: 404});
const url = 'my url';
const fetchOptions = {method: 'DELETE'};
sinon.stub(authService, 'fetch').resolves(response);
const startTime = 123;
sinon.stub(Date, 'now').returns(startTime);
const req = {url, fetchOptions};
await element._restApiHelper.fetch(req);
assert.isTrue(logStub.calledOnce);
assert.isTrue(logStub.calledWith(req, startTime, response.status));
});
test('_logCall only reports requests with anonymized URLss', async () => {
sinon.stub(Date, 'now').returns(200);
const handler = sinon.stub();
addListenerForTest(document, 'gr-rpc-log', handler);
element._restApiHelper._logCall({url: 'url'}, 100, 200);
assert.isFalse(handler.called);
element._restApiHelper._logCall(
{url: 'url', anonymizedUrl: 'not url'},
100,
200
);
await waitEventLoop();
assert.isTrue(handler.calledOnce);
});
test('ported comment errors do not trigger error dialog', () => {
const change = createChange();
const handler = sinon.stub();
addListenerForTest(document, 'server-error', handler);
sinon.stub(element._restApiHelper, 'fetchJSON').resolves({
ok: false,
} as unknown as ParsedJSON);
element.getPortedComments(change._number, CURRENT);
assert.isFalse(handler.called);
});
test('ported drafts are not requested user is not logged in', () => {
const change = createChange();
sinon.stub(element, 'getLoggedIn').resolves(false);
const getChangeURLAndFetchStub = sinon.stub(
element,
'_getChangeURLAndFetch'
);
element.getPortedDrafts(change._number, CURRENT);
assert.isFalse(getChangeURLAndFetchStub.called);
});
test('saveChangeStarred', async () => {
sinon.stub(element, 'getFromProjectLookup').resolves('test' as RepoName);
const sendStub = sinon.stub(element._restApiHelper, 'send').resolves();
await element.saveChangeStarred(123 as NumericChangeId, true);
assert.isTrue(sendStub.calledOnce);
assert.deepEqual(sendStub.lastCall.args[0], {
method: HttpMethod.PUT,
url: '/accounts/self/starred.changes/test~123',
anonymizedUrl: '/accounts/self/starred.changes/*',
});
await element.saveChangeStarred(456 as NumericChangeId, false);
assert.isTrue(sendStub.calledTwice);
assert.deepEqual(sendStub.lastCall.args[0], {
method: HttpMethod.DELETE,
url: '/accounts/self/starred.changes/test~456',
anonymizedUrl: '/accounts/self/starred.changes/*',
});
});
suite('getDocsBaseUrl tests', () => {
test('null config', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(true);
const docsBaseUrl = await element.getDocsBaseUrl(undefined);
assert.equal(
probePathMock.lastCall.args[0],
`${getBaseUrl()}/Documentation/index.html`
);
assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
});
test('no doc config', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(true);
const config: ServerInfo = {
...createServerInfo(),
gerrit: createGerritInfo(),
};
const docsBaseUrl = await element.getDocsBaseUrl(config);
assert.equal(
probePathMock.lastCall.args[0],
`${getBaseUrl()}/Documentation/index.html`
);
assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
});
test('has doc config', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(true);
const config: ServerInfo = {
...createServerInfo(),
gerrit: {...createGerritInfo(), doc_url: 'foobar'},
};
const docsBaseUrl = await element.getDocsBaseUrl(config);
assert.isFalse(probePathMock.called);
assert.equal(docsBaseUrl, 'foobar');
});
test('no probe', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(false);
const docsBaseUrl = await element.getDocsBaseUrl(undefined);
assert.equal(
probePathMock.lastCall.args[0],
`${getBaseUrl()}/Documentation/index.html`
);
assert.isNotOk(docsBaseUrl);
});
});
});