/** * @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'; 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(() => { element = basicFixture.instantiate(); stub('gr-rest-api-interface', { getAccount() { return Promise.resolve(null); }, }); repoStub = sinon.stub(element.restApiService, 'getRepo').returns( Promise.resolve(repoRes)); element._loading = false; element._ownerOf = []; element._canUpload = false; }); test('_repoChanged called when repo name changes', () => { sinon.stub(element, '_repoChanged'); element.repo = 'New Repo'; assert.isTrue(element._repoChanged.called); }); test('_repoChanged', done => { const accessStub = sinon.stub(element.restApiService, '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 = sinon.stub(element.restApiService, 'getCapabilities'); capabilitiesStub.returns(Promise.resolve(capabilitiesRes)); element._repoChanged('New Repo').then(() => { 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'); return element._repoChanged('Another New Repo'); }) .then(() => { assert.deepEqual(element._sections, toSortedPermissionsArray(accessRes2.local)); assert.equal(getComputedStyle(element.shadowRoot .querySelector('.weblinks')).display, 'none'); done(); }); }); test('_repoChanged when repo changes to undefined returns', done => { const capabilitiesRes = { accessDatabase: { id: 'accessDatabase', name: 'Access Database', }, }; const accessStub = sinon.stub(element.restApiService, 'getRepoAccessRights') .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2)))); const capabilitiesStub = sinon.stub(element.restApiService, 'getCapabilities').returns(Promise.resolve(capabilitiesRes)); element._repoChanged().then(() => { assert.isFalse(accessStub.called); assert.isFalse(capabilitiesStub.called); assert.isFalse(repoStub.called); done(); }); }); 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', () => { element._local = {}; element._ownerOf = []; sinon.stub(element, '_computeParentHref'); // 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 it 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 = { name: 'another-repo', }; // When there is a parent project, the link should be displayed. flush(); 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', () => { element._inheritFromFilter = 'foo bar baz'; element._handleUpdateInheritFrom({detail: {value: 'abc+123'}}); 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', done => { const response = {status: 404}; sinon.stub( element.restApiService, 'getRepoAccessRights') .callsFake((repoName, errFn) => { errFn(response); }); element.addEventListener('page-error', e => { assert.deepEqual(e.detail.response, response); done(); }); element.repo = 'test'; }); suite('with defined sections', () => { const testEditSaveCancelBtns = (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', }; flush(); assert.equal(getComputedStyle(element.shadowRoot .querySelector('#editInheritFromInput')) .display, 'none'); MockInteractions.tap(element.$.editBtn); 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(() => { // 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)); flush(); }); test('removing an added section', () => { element.editing = true; assert.equal(element._sections.length, 1); element.shadowRoot .querySelector('gr-access-section').dispatchEvent( new CustomEvent('added-section-removed', { composed: true, bubbles: true, })); 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', () => { element._canUpload = true; testEditSaveCancelBtns(false, true); }); test('button visibility for ref owner', () => { element._ownerOf = ['refs/for/*']; testEditSaveCancelBtns(true, false); }); test('button visibility for ref owner and upload', () => { element._ownerOf = ['refs/for/*']; element._canUpload = true; testEditSaveCancelBtns(true, false); }); test('_handleAccessModified called with event fired', () => { sinon.spy(element, '_handleAccessModified'); element.dispatchEvent( new CustomEvent('access-modified', { composed: true, bubbles: true, })); assert.isTrue(element._handleAccessModified.called); }); test('_handleAccessModified called when parent changes', () => { element._inheritsFrom = { id: 'test-project', }; 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, })); assert.isTrue(element._handleAccessModified.called); }); test('_handleSaveForReview', () => { const saveStub = sinon.stub(element.restApiService, 'setRepoAccessRightsForReview'); sinon.stub(element, '_computeAddAndRemove').returns({ add: {}, remove: {}, }); element._handleSaveForReview(); 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', () => { element._inheritsFrom = { id: 'test-project', }; element._originalInheritsFrom = { id: 'test-project-original', }; assert.deepEqual(element._computeAddAndRemove(), { parent: 'test-project', add: {}, remove: {}, }); }); test('_handleSaveForReview new parent with spaces', () => { element._inheritsFrom = {id: 'spaces+in+project+name'}; element._originalInheritsFrom = {id: 'old-project'}; assert.deepEqual(element._computeAddAndRemove(), { parent: 'spaces in project name', add: {}, remove: {}, }); }); test('_handleSaveForReview rules', () => { // Delete a rule. element._local['refs/*'].permissions.owner.rules[123].deleted = true; 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; 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', () => { // 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'}}); 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; 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; 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', () => { // 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(); 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'}}); assert.deepEqual(element._computeAddAndRemove(), expectedInput); // Modify a section reference. element._local['refs/*'].updatedId = 'refs/for/bar'; element._local['refs/*'].modified = true; 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: {}, }, }, }; assert.deepEqual(element._computeAddAndRemove(), expectedInput); // Delete a section. element._local['refs/*'].deleted = true; expectedInput = { add: {}, remove: { 'refs/*': { permissions: {}, }, }, }; assert.deepEqual(element._computeAddAndRemove(), expectedInput); }); test('_computeAddAndRemove new section', () => { // Add a new permission to a section let expectedInput = { add: { 'refs/for/*': { added: true, permissions: {}, }, }, remove: {}, }; MockInteractions.tap(element.$.addReferenceBtn); 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(); 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'}}); flush(); assert.deepEqual(element._computeAddAndRemove(), expectedInput); // Modify a the reference from the default value. element._local['refs/for/*'].updatedId = 'refs/for/new'; 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', () => { // 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; 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; assert.deepEqual(element._computeAddAndRemove(), expectedInput); // Also modify a different rule inside of another permission. element._local['refs/*'].permissions.read.modified = true; 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; 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'}}); 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; 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; 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(); 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'; 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; 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); newSection = dom(element.root) .querySelectorAll('gr-access-section')[2]; newSection._handleAddPermission(); 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'; 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', () => { // Unsaved changes are discarded when editing is cancelled. MockInteractions.tap(element.$.editBtn); assert.equal(element._sections.length, 1); assert.equal(Object.keys(element._local).length, 1); MockInteractions.tap(element.$.addReferenceBtn); assert.equal(element._sections.length, 2); assert.equal(Object.keys(element._local).length, 2); MockInteractions.tap(element.$.editBtn); assert.equal(element._sections.length, 1); assert.equal(Object.keys(element._local).length, 1); }); test('_handleSave', done => { const repoAccessInput = { add: { 'refs/*': { permissions: { owner: { rules: { 123: {action: 'DENY', modified: true}, }, }, }, }, }, remove: { 'refs/*': { permissions: { owner: { rules: { 123: {}, }, }, }, }, }, }; sinon.stub(element.restApiService, 'getRepoAccessRights').returns( Promise.resolve(JSON.parse(JSON.stringify(accessRes)))); sinon.stub(GerritNav, 'navigateToChange'); let resolver; const saveStub = sinon.stub(element.restApiService, 'setRepoAccessRights') .returns(new Promise(r => resolver = r)); element.repo = 'test-repo'; sinon.stub(element, '_computeAddAndRemove').returns(repoAccessInput); element._modified = true; MockInteractions.tap(element.$.saveBtn); assert.equal(element.$.saveBtn.hasAttribute('loading'), true); resolver({_number: 1}); flush(() => { assert.isTrue(saveStub.called); assert.isTrue(GerritNav.navigateToChange.notCalled); done(); }); }); test('_handleSaveForReview', done => { const repoAccessInput = { add: { 'refs/*': { permissions: { owner: { rules: { 123: {action: 'DENY', modified: true}, }, }, }, }, }, remove: { 'refs/*': { permissions: { owner: { rules: { 123: {}, }, }, }, }, }, }; sinon.stub(element.restApiService, 'getRepoAccessRights').returns( Promise.resolve(JSON.parse(JSON.stringify(accessRes)))); sinon.stub(GerritNav, 'navigateToChange'); let resolver; const saveForReviewStub = sinon.stub(element.restApiService, 'setRepoAccessRightsForReview') .returns(new Promise(r => resolver = r)); element.repo = 'test-repo'; sinon.stub(element, '_computeAddAndRemove').returns(repoAccessInput); element._modified = true; MockInteractions.tap(element.$.saveReviewBtn); assert.equal(element.$.saveReviewBtn.hasAttribute('loading'), true); resolver({_number: 1}); flush(() => { assert.isTrue(saveForReviewStub.called); assert.isTrue(GerritNav.navigateToChange .lastCall.calledWithExactly({_number: 1})); done(); }); }); }); });