blob: 1ccfd5e148d82d4e76fe36887447fbf797cd6ff7 [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../../../test/common-test-setup-karma.js';
import './gr-repo-access.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {toSortedPermissionsArray} from '../../../utils/access-util.js';
import {
addListenerForTest,
mockPromise,
stubRestApi,
} from '../../../test/test-utils.js';
const basicFixture = fixtureFromElement('gr-repo-access');
suite('gr-repo-access tests', () => {
let element;
let repoStub;
const accessRes = {
local: {
'refs/*': {
permissions: {
owner: {
rules: {
234: {action: 'ALLOW'},
123: {action: 'DENY'},
},
},
read: {
rules: {
234: {action: 'ALLOW'},
},
},
},
},
},
groups: {
Administrators: {
name: 'Administrators',
},
Maintainers: {
name: 'Maintainers',
},
},
config_web_links: [{
name: 'gitiles',
target: '_blank',
url: 'https://my/site/+log/123/project.config',
}],
can_upload: true,
};
const accessRes2 = {
local: {
GLOBAL_CAPABILITIES: {
permissions: {
accessDatabase: {
rules: {
group1: {
action: 'ALLOW',
},
},
},
},
},
},
};
const repoRes = {
labels: {
'Code-Review': {
values: {
' 0': 'No score',
'-1': 'I would prefer this is not merged as is',
'-2': 'This shall not be merged',
'+1': 'Looks good to me, but someone else must approve',
'+2': 'Looks good to me, approved',
},
},
},
};
const capabilitiesRes = {
accessDatabase: {
id: 'accessDatabase',
name: 'Access Database',
},
createAccount: {
id: 'createAccount',
name: 'Create Account',
},
};
setup(async () => {
element = basicFixture.instantiate();
stubRestApi('getAccount').returns(Promise.resolve(null));
repoStub = stubRestApi('getRepo').returns(Promise.resolve(repoRes));
element._loading = false;
element._ownerOf = [];
element._canUpload = false;
await flush();
});
test('_repoChanged called when repo name changes', async () => {
sinon.stub(element, '_repoChanged');
element.repo = 'New Repo';
await flush();
assert.isTrue(element._repoChanged.called);
});
test('_repoChanged', async () => {
const accessStub = stubRestApi(
'getRepoAccessRights');
accessStub.withArgs('New Repo').returns(
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
accessStub.withArgs('Another New Repo')
.returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = stubRestApi(
'getCapabilities');
capabilitiesStub.returns(Promise.resolve(capabilitiesRes));
await element._repoChanged('New Repo');
assert.isTrue(accessStub.called);
assert.isTrue(capabilitiesStub.called);
assert.isTrue(repoStub.called);
assert.isNotOk(element._inheritsFrom);
assert.deepEqual(element._local, accessRes.local);
assert.deepEqual(element._sections,
toSortedPermissionsArray(accessRes.local));
assert.deepEqual(element._labels, repoRes.labels);
assert.equal(getComputedStyle(element.shadowRoot
.querySelector('.weblinks')).display,
'block');
await element._repoChanged('Another New Repo');
assert.deepEqual(element._sections,
toSortedPermissionsArray(accessRes2.local));
assert.equal(getComputedStyle(element.shadowRoot
.querySelector('.weblinks')).display,
'none');
});
test('_repoChanged when repo changes to undefined returns', async () => {
const capabilitiesRes = {
accessDatabase: {
id: 'accessDatabase',
name: 'Access Database',
},
};
const accessStub = stubRestApi('getRepoAccessRights')
.returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = stubRestApi(
'getCapabilities').returns(Promise.resolve(capabilitiesRes));
await element._repoChanged();
assert.isFalse(accessStub.called);
assert.isFalse(capabilitiesStub.called);
assert.isFalse(repoStub.called);
});
test('_computeParentHref', () => {
const repoName = 'test-repo';
assert.equal(element._computeParentHref(repoName),
'/admin/repos/test-repo,access');
});
test('_computeMainClass', () => {
let ownerOf = ['refs/*'];
const editing = true;
const canUpload = false;
assert.equal(element._computeMainClass(ownerOf, canUpload), 'admin');
assert.equal(element._computeMainClass(ownerOf, canUpload, editing),
'admin editing');
ownerOf = [];
assert.equal(element._computeMainClass(ownerOf, canUpload), '');
assert.equal(element._computeMainClass(ownerOf, canUpload, editing),
'editing');
});
test('inherit section', async () => {
element._local = {};
element._ownerOf = [];
sinon.stub(element, '_computeParentHref');
await flush();
// Nothing should appear when no inherit from and not in edit mode.
assert.equal(getComputedStyle(element.$.inheritsFrom).display, 'none');
// The autocomplete should be hidden, and the link should be displayed.
assert.isFalse(element._computeParentHref.called);
// When in edit mode, the autocomplete should appear.
element._editing = true;
// When editing, the autocomplete should still not be shown.
assert.equal(getComputedStyle(element.$.inheritsFrom).display, 'none');
element._editing = false;
element._inheritsFrom = {
id: '1234',
name: 'another-repo',
};
await flush();
// When there is a parent project, the link should be displayed.
assert.notEqual(getComputedStyle(element.$.inheritsFrom).display, 'none');
assert.notEqual(getComputedStyle(element.$.inheritFromName).display,
'none');
assert.equal(getComputedStyle(element.$.editInheritFromInput).display,
'none');
assert.isTrue(element._computeParentHref.called);
element._editing = true;
// When editing, the autocomplete should be shown.
assert.notEqual(getComputedStyle(element.$.inheritsFrom).display, 'none');
assert.equal(getComputedStyle(element.$.inheritFromName).display, 'none');
assert.notEqual(getComputedStyle(element.$.editInheritFromInput).display,
'none');
});
test('_handleUpdateInheritFrom', async () => {
element._inheritFromFilter = 'foo bar baz';
element._handleUpdateInheritFrom({detail: {value: 'abc+123'}});
await flush();
assert.isOk(element._inheritsFrom);
assert.equal(element._inheritsFrom.id, 'abc+123');
assert.equal(element._inheritsFrom.name, 'foo bar baz');
});
test('_computeLoadingClass', () => {
assert.equal(element._computeLoadingClass(true), 'loading');
assert.equal(element._computeLoadingClass(false), '');
});
test('fires page-error', async () => {
const response = {status: 404};
stubRestApi('getRepoAccessRights').callsFake((repoName, errFn) => {
errFn(response);
return Promise.resolve(undefined);
});
const promise = mockPromise();
addListenerForTest(document, 'page-error', e => {
assert.deepEqual(e.detail.response, response);
promise.resolve();
});
element.repo = 'test';
await promise;
});
suite('with defined sections', () => {
const testEditSaveCancelBtns = async (
shouldShowSave,
shouldShowSaveReview
) => {
// Edit button is visible and Save button is hidden.
assert.equal(getComputedStyle(element.$.saveReviewBtn).display, 'none');
assert.equal(getComputedStyle(element.$.saveBtn).display, 'none');
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
assert.equal(element.$.editBtn.innerText, 'EDIT');
assert.equal(
getComputedStyle(element.$.editInheritFromInput).display,
'none'
);
element._inheritsFrom = {
id: 'test-project',
};
await flush();
assert.equal(
getComputedStyle(
element.shadowRoot.querySelector('#editInheritFromInput')
).display,
'none'
);
MockInteractions.tap(element.$.editBtn);
await flush();
// Edit button changes to Cancel button, and Save button is visible but
// disabled.
assert.equal(element.$.editBtn.innerText, 'CANCEL');
if (shouldShowSaveReview) {
assert.notEqual(
getComputedStyle(element.$.saveReviewBtn).display,
'none'
);
assert.isTrue(element.$.saveReviewBtn.disabled);
}
if (shouldShowSave) {
assert.notEqual(getComputedStyle(element.$.saveBtn).display, 'none');
assert.isTrue(element.$.saveBtn.disabled);
}
assert.notEqual(
getComputedStyle(
element.shadowRoot.querySelector('#editInheritFromInput')
).display,
'none'
);
// Save button should be enabled after access is modified
element.dispatchEvent(
new CustomEvent('access-modified', {
composed: true,
bubbles: true,
})
);
if (shouldShowSaveReview) {
assert.isFalse(element.$.saveReviewBtn.disabled);
}
if (shouldShowSave) {
assert.isFalse(element.$.saveBtn.disabled);
}
};
setup(async () => {
// Create deep copies of these objects so the originals are not modified
// by any tests.
element._local = JSON.parse(JSON.stringify(accessRes.local));
element._ownerOf = [];
element._sections = toSortedPermissionsArray(element._local);
element._groups = JSON.parse(JSON.stringify(accessRes.groups));
element._capabilities = JSON.parse(JSON.stringify(capabilitiesRes));
element._labels = JSON.parse(JSON.stringify(repoRes.labels));
await flush();
});
test('removing an added section', async () => {
element.editing = true;
await flush();
assert.equal(element._sections.length, 1);
element.shadowRoot
.querySelector('gr-access-section').dispatchEvent(
new CustomEvent('added-section-removed', {
composed: true, bubbles: true,
}));
await flush();
assert.equal(element._sections.length, 0);
});
test('button visibility for non ref owner', () => {
assert.equal(getComputedStyle(element.$.saveReviewBtn).display, 'none');
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
});
test('button visibility for non ref owner with upload privilege',
async () => {
element._canUpload = true;
await flush();
testEditSaveCancelBtns(false, true);
});
test('button visibility for ref owner', async () => {
element._ownerOf = ['refs/for/*'];
await flush();
testEditSaveCancelBtns(true, false);
});
test('button visibility for ref owner and upload', async () => {
element._ownerOf = ['refs/for/*'];
element._canUpload = true;
await flush();
testEditSaveCancelBtns(true, false);
});
test('_handleAccessModified called with event fired', async () => {
sinon.spy(element, '_handleAccessModified');
element.dispatchEvent(
new CustomEvent('access-modified', {
composed: true, bubbles: true,
}));
await flush();
assert.isTrue(element._handleAccessModified.called);
});
test('_handleAccessModified called when parent changes', async () => {
element._inheritsFrom = {
id: 'test-project',
};
await flush();
element.shadowRoot.querySelector('#editInheritFromInput').dispatchEvent(
new CustomEvent('commit', {
detail: {},
composed: true, bubbles: true,
}));
sinon.spy(element, '_handleAccessModified');
element.dispatchEvent(
new CustomEvent('access-modified', {
detail: {},
composed: true, bubbles: true,
}));
await flush();
assert.isTrue(element._handleAccessModified.called);
});
test('_handleSaveForReview', async () => {
const saveStub =
stubRestApi('setRepoAccessRightsForReview');
sinon.stub(element, '_computeAddAndRemove').returns({
add: {},
remove: {},
});
element._handleSaveForReview();
await flush();
assert.isFalse(saveStub.called);
});
test('_recursivelyRemoveDeleted', () => {
const obj = {
'refs/*': {
permissions: {
owner: {
rules: {
234: {action: 'ALLOW'},
123: {action: 'DENY', deleted: true},
},
},
read: {
deleted: true,
rules: {
234: {action: 'ALLOW'},
},
},
},
},
};
const expectedResult = {
'refs/*': {
permissions: {
owner: {
rules: {
234: {action: 'ALLOW'},
},
},
},
},
};
element._recursivelyRemoveDeleted(obj);
assert.deepEqual(obj, expectedResult);
});
test('_recursivelyUpdateAddRemoveObj on new added section', () => {
const obj = {
'refs/for/*': {
permissions: {
'label-Code-Review': {
rules: {
e798fed07afbc9173a587f876ef8760c78d240c1: {
min: -2,
max: 2,
action: 'ALLOW',
added: true,
},
},
added: true,
label: 'Code-Review',
},
'labelAs-Code-Review': {
rules: {
'ldap:gerritcodereview-eng': {
min: -2,
max: 2,
action: 'ALLOW',
added: true,
deleted: true,
},
},
added: true,
label: 'Code-Review',
},
},
added: true,
},
};
const expectedResult = {
add: {
'refs/for/*': {
permissions: {
'label-Code-Review': {
rules: {
e798fed07afbc9173a587f876ef8760c78d240c1: {
min: -2,
max: 2,
action: 'ALLOW',
added: true,
},
},
added: true,
label: 'Code-Review',
},
'labelAs-Code-Review': {
rules: {},
added: true,
label: 'Code-Review',
},
},
added: true,
},
},
remove: {},
};
const updateObj = {add: {}, remove: {}};
element._recursivelyUpdateAddRemoveObj(obj, updateObj);
assert.deepEqual(updateObj, expectedResult);
});
test('_handleSaveForReview with no changes', () => {
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
});
test('_handleSaveForReview parent change', async () => {
element._inheritsFrom = {
id: 'test-project',
};
element._originalInheritsFrom = {
id: 'test-project-original',
};
await flush();
assert.deepEqual(element._computeAddAndRemove(), {
parent: 'test-project', add: {}, remove: {},
});
});
test('_handleSaveForReview new parent with spaces', async () => {
element._inheritsFrom = {id: 'spaces+in+project+name'};
element._originalInheritsFrom = {id: 'old-project'};
await flush();
assert.deepEqual(element._computeAddAndRemove(), {
parent: 'spaces in project name', add: {}, remove: {},
});
});
test('_handleSaveForReview rules', async () => {
// Delete a rule.
element._local['refs/*'].permissions.owner.rules[123].deleted = true;
await flush();
let expectedInput = {
add: {},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {},
},
},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Undo deleting a rule.
delete element._local['refs/*'].permissions.owner.rules[123].deleted;
// Modify a rule.
element._local['refs/*'].permissions.owner.rules[123].modified = true;
await flush();
expectedInput = {
add: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {action: 'DENY', modified: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {},
},
},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('_computeAddAndRemove permissions', async () => {
// Add a new rule to a permission.
let expectedInput = {
add: {
'refs/*': {
permissions: {
owner: {
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
},
},
},
},
},
},
remove: {},
};
element.shadowRoot
.querySelector('gr-access-section').shadowRoot
.querySelector('gr-permission')
._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Remove the added rule.
delete element._local['refs/*'].permissions.owner.rules.Maintainers;
// Delete a permission.
element._local['refs/*'].permissions.owner.deleted = true;
await flush();
expectedInput = {
add: {},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Undo delete permission.
delete element._local['refs/*'].permissions.owner.deleted;
// Modify a permission.
element._local['refs/*'].permissions.owner.modified = true;
await flush();
expectedInput = {
add: {
'refs/*': {
permissions: {
owner: {
modified: true,
rules: {
234: {action: 'ALLOW'},
123: {action: 'DENY'},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('_computeAddAndRemove sections', async () => {
// Add a new permission to a section
let expectedInput = {
add: {
'refs/*': {
permissions: {
'label-Code-Review': {
added: true,
rules: {},
label: 'Code-Review',
},
},
},
},
remove: {},
};
element.shadowRoot
.querySelector('gr-access-section')._handleAddPermission();
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a new rule to the new permission.
expectedInput = {
add: {
'refs/*': {
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
min: -2,
max: 2,
action: 'ALLOW',
added: true,
},
},
label: 'Code-Review',
},
},
},
},
remove: {},
};
const newPermission =
dom(element.shadowRoot
.querySelector('gr-access-section').root).querySelectorAll(
'gr-permission')[2];
newPermission._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify a section reference.
element._local['refs/*'].updatedId = 'refs/for/bar';
element._local['refs/*'].modified = true;
await flush();
expectedInput = {
add: {
'refs/for/bar': {
modified: true,
updatedId: 'refs/for/bar',
permissions: {
'owner': {
rules: {
234: {action: 'ALLOW'},
123: {action: 'DENY'},
},
},
'read': {
rules: {
234: {action: 'ALLOW'},
},
},
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
min: -2,
max: 2,
action: 'ALLOW',
added: true,
},
},
label: 'Code-Review',
},
},
},
},
remove: {
'refs/*': {
permissions: {},
},
},
};
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Delete a section.
element._local['refs/*'].deleted = true;
await flush();
expectedInput = {
add: {},
remove: {
'refs/*': {
permissions: {},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('_computeAddAndRemove new section', async () => {
// Add a new permission to a section
let expectedInput = {
add: {
'refs/for/*': {
added: true,
permissions: {},
},
},
remove: {},
};
MockInteractions.tap(element.$.addReferenceBtn);
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
expectedInput = {
add: {
'refs/for/*': {
added: true,
permissions: {
'label-Code-Review': {
added: true,
rules: {},
label: 'Code-Review',
},
},
},
},
remove: {},
};
const newSection = dom(element.root)
.querySelectorAll('gr-access-section')[1];
newSection._handleAddPermission();
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add rule to the new permission.
expectedInput = {
add: {
'refs/for/*': {
added: true,
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
},
remove: {},
};
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify a the reference from the default value.
element._local['refs/for/*'].updatedId = 'refs/for/new';
await flush();
expectedInput = {
add: {
'refs/for/new': {
added: true,
updatedId: 'refs/for/new',
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
},
remove: {},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('_computeAddAndRemove combinations', async () => {
// Modify rule and delete permission that it is inside of.
element._local['refs/*'].permissions.owner.rules[123].modified = true;
element._local['refs/*'].permissions.owner.deleted = true;
await flush();
let expectedInput = {
add: {},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Delete rule and delete permission that it is inside of.
element._local['refs/*'].permissions.owner.rules[123].modified = false;
element._local['refs/*'].permissions.owner.rules[123].deleted = true;
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Also modify a different rule inside of another permission.
element._local['refs/*'].permissions.read.modified = true;
await flush();
expectedInput = {
add: {
'refs/*': {
permissions: {
read: {
modified: true,
rules: {
234: {action: 'ALLOW'},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
read: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify both permissions with an exclusive bit. Owner is still
// deleted.
element._local['refs/*'].permissions.owner.exclusive = true;
element._local['refs/*'].permissions.owner.modified = true;
element._local['refs/*'].permissions.read.exclusive = true;
element._local['refs/*'].permissions.read.modified = true;
await flush();
expectedInput = {
add: {
'refs/*': {
permissions: {
read: {
exclusive: true,
modified: true,
rules: {
234: {action: 'ALLOW'},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
read: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a rule to the existing permission;
const readPermission =
dom(element.shadowRoot
.querySelector('gr-access-section').root).querySelectorAll(
'gr-permission')[1];
readPermission._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
await flush();
expectedInput = {
add: {
'refs/*': {
permissions: {
read: {
exclusive: true,
modified: true,
rules: {
234: {action: 'ALLOW'},
Maintainers: {action: 'ALLOW', added: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
read: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Change one of the refs
element._local['refs/*'].updatedId = 'refs/for/bar';
element._local['refs/*'].modified = true;
await flush();
expectedInput = {
add: {
'refs/for/bar': {
modified: true,
updatedId: 'refs/for/bar',
permissions: {
read: {
exclusive: true,
modified: true,
rules: {
234: {action: 'ALLOW'},
Maintainers: {action: 'ALLOW', added: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
expectedInput = {
add: {},
remove: {
'refs/*': {
permissions: {},
},
},
};
element._local['refs/*'].deleted = true;
await flush();
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a new section.
MockInteractions.tap(element.$.addReferenceBtn);
let newSection = dom(element.root)
.querySelectorAll('gr-access-section')[1];
newSection._handleAddPermission();
await flush();
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
// Modify a the reference from the default value.
element._local['refs/for/*'].updatedId = 'refs/for/new';
await flush();
expectedInput = {
add: {
'refs/for/new': {
added: true,
updatedId: 'refs/for/new',
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
},
remove: {
'refs/*': {
permissions: {},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify newly added rule inside new ref.
element._local['refs/for/*'].permissions['label-Code-Review'].
rules['Maintainers'].modified = true;
await flush();
expectedInput = {
add: {
'refs/for/new': {
added: true,
updatedId: 'refs/for/new',
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
modified: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
},
remove: {
'refs/*': {
permissions: {},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Add a second new section.
MockInteractions.tap(element.$.addReferenceBtn);
await flush();
newSection = dom(element.root)
.querySelectorAll('gr-access-section')[2];
newSection._handleAddPermission();
await flush();
newSection.shadowRoot
.querySelector('gr-permission')._handleAddRuleItem(
{detail: {value: 'Maintainers'}});
// Modify a the reference from the default value.
element._local['refs/for/**'].updatedId = 'refs/for/new2';
await flush();
expectedInput = {
add: {
'refs/for/new': {
added: true,
updatedId: 'refs/for/new',
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
modified: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
'refs/for/new2': {
added: true,
updatedId: 'refs/for/new2',
permissions: {
'label-Code-Review': {
added: true,
rules: {
Maintainers: {
action: 'ALLOW',
added: true,
max: 2,
min: -2,
},
},
label: 'Code-Review',
},
},
},
},
remove: {
'refs/*': {
permissions: {},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('Unsaved added refs are discarded when edit cancelled', async () => {
// Unsaved changes are discarded when editing is cancelled.
MockInteractions.tap(element.$.editBtn);
await flush();
assert.equal(element._sections.length, 1);
assert.equal(Object.keys(element._local).length, 1);
MockInteractions.tap(element.$.addReferenceBtn);
await flush();
assert.equal(element._sections.length, 2);
assert.equal(Object.keys(element._local).length, 2);
MockInteractions.tap(element.$.editBtn);
await flush();
assert.equal(element._sections.length, 1);
assert.equal(Object.keys(element._local).length, 1);
});
test('_handleSave', async () => {
const repoAccessInput = {
add: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {action: 'DENY', modified: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {},
},
},
},
},
},
};
stubRestApi('getRepoAccessRights').returns(
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sinon.stub(GerritNav, 'navigateToChange');
let resolver;
const saveStub = stubRestApi(
'setRepoAccessRights')
.returns(new Promise(r => resolver = r));
element.repo = 'test-repo';
sinon.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
element._modified = true;
MockInteractions.tap(element.$.saveBtn);
await flush();
assert.equal(element.$.saveBtn.hasAttribute('loading'), true);
resolver({_number: 1});
await flush();
assert.isTrue(saveStub.called);
assert.isTrue(GerritNav.navigateToChange.notCalled);
});
test('_handleSaveForReview', async () => {
const repoAccessInput = {
add: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {action: 'DENY', modified: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {},
},
},
},
},
},
};
stubRestApi('getRepoAccessRights').returns(
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sinon.stub(GerritNav, 'navigateToChange');
let resolver;
const saveForReviewStub = stubRestApi(
'setRepoAccessRightsForReview')
.returns(new Promise(r => resolver = r));
element.repo = 'test-repo';
sinon.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
element._modified = true;
MockInteractions.tap(element.$.saveReviewBtn);
await flush();
assert.equal(element.$.saveReviewBtn.hasAttribute('loading'), true);
resolver({_number: 1});
await flush();
assert.isTrue(saveForReviewStub.called);
assert.isTrue(GerritNav.navigateToChange
.lastCall.calledWithExactly({_number: 1}));
});
});
});