| /** |
| * @license |
| * Copyright 2017 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import '../../../test/common-test-setup'; |
| import './gr-repo-access'; |
| import {GrRepoAccess} from './gr-repo-access'; |
| import {navigationToken} from '../../core/gr-navigation/gr-navigation'; |
| import {toSortedPermissionsArray} from '../../../utils/access-util'; |
| import { |
| addListenerForTest, |
| mockPromise, |
| queryAll, |
| queryAndAssert, |
| stubRestApi, |
| } from '../../../test/test-utils'; |
| import { |
| ChangeInfo, |
| GitRef, |
| RepoName, |
| UrlEncodedRepoName, |
| } from '../../../types/common'; |
| import {PermissionAction} from '../../../constants/constants'; |
| import {AutocompleteCommitEvent, PageErrorEvent} from '../../../types/events'; |
| import {GrButton} from '../../shared/gr-button/gr-button'; |
| import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete'; |
| import {GrAccessSection} from '../gr-access-section/gr-access-section'; |
| import {GrPermission} from '../gr-permission/gr-permission'; |
| import {createChange} from '../../../test/test-data-generators'; |
| import {fixture, html, assert} from '@open-wc/testing'; |
| import {testResolver} from '../../../test/common-test-setup'; |
| |
| suite('gr-repo-access tests', () => { |
| let element: GrRepoAccess; |
| |
| let repoStub: sinon.SinonStub; |
| |
| const accessRes = { |
| local: { |
| 'refs/*': { |
| permissions: { |
| owner: { |
| rules: { |
| 234: {action: PermissionAction.ALLOW}, |
| 123: {action: PermissionAction.DENY}, |
| }, |
| }, |
| read: { |
| rules: { |
| 234: {action: PermissionAction.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: PermissionAction.ALLOW, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }; |
| const repoRes = { |
| id: '' as UrlEncodedRepoName, |
| labels: { |
| 'Code-Review': { |
| values: { |
| '0': 'No score', |
| '-1': 'I would prefer this is not submitted as is', |
| '-2': 'This shall not be submitted', |
| '+1': 'Looks good to me, but someone else must approve', |
| '+2': 'Looks good to me, approved', |
| }, |
| default_value: 0, |
| }, |
| }, |
| }; |
| const capabilitiesRes = { |
| accessDatabase: { |
| id: 'accessDatabase', |
| name: 'Access Database', |
| }, |
| createAccount: { |
| id: 'createAccount', |
| name: 'Create Account', |
| }, |
| }; |
| |
| setup(async () => { |
| element = await fixture<GrRepoAccess>(html` |
| <gr-repo-access></gr-repo-access> |
| `); |
| stubRestApi('getAccount').returns(Promise.resolve(undefined)); |
| repoStub = stubRestApi('getRepo').returns(Promise.resolve(repoRes)); |
| element.loading = false; |
| element.ownerOf = []; |
| element.canUpload = false; |
| await element.updateComplete; |
| }); |
| |
| test('render', () => { |
| assert.shadowDom.equal( |
| element, |
| /* HTML */ ` |
| <div class="main"> |
| <div id="loading">Loading...</div> |
| <div id="loadedContent"> |
| <h3 class="heading-3" id="inheritsFrom"> |
| <span class="rightsText"> Rights Inherit From </span> |
| <a href="" id="inheritFromName" rel="noopener"> </a> |
| <gr-autocomplete id="editInheritFromInput"> </gr-autocomplete> |
| </h3> |
| <div class="weblinks">History:</div> |
| <div class="referenceContainer"> |
| <gr-button |
| aria-disabled="false" |
| id="addReferenceBtn" |
| role="button" |
| tabindex="0" |
| > |
| Add Reference |
| </gr-button> |
| </div> |
| <div> |
| <gr-button |
| aria-disabled="false" |
| id="editBtn" |
| role="button" |
| tabindex="0" |
| > |
| Edit |
| </gr-button> |
| <gr-button |
| aria-disabled="true" |
| disabled="" |
| class="invisible" |
| id="saveBtn" |
| primary="" |
| role="button" |
| tabindex="-1" |
| > |
| Save |
| </gr-button> |
| <gr-button |
| aria-disabled="false" |
| class="invisible" |
| id="saveReviewBtn" |
| primary="" |
| role="button" |
| tabindex="0" |
| > |
| Save for review |
| </gr-button> |
| </div> |
| </div> |
| </div> |
| ` |
| ); |
| }); |
| |
| test('_repoChanged called when repo name changes', async () => { |
| const repoChangedStub = sinon.stub(element, '_repoChanged'); |
| element.repo = 'New Repo' as RepoName; |
| await element.updateComplete; |
| assert.isTrue(repoChangedStub.called); |
| }); |
| |
| test('_repoChanged', async () => { |
| const accessStub = stubRestApi('getRepoAccessRights'); |
| |
| accessStub |
| .withArgs('New Repo' as RepoName) |
| .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes)))); |
| accessStub |
| .withArgs('Another New Repo' as RepoName) |
| .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2)))); |
| const capabilitiesStub = stubRestApi('getCapabilities'); |
| capabilitiesStub.returns(Promise.resolve(capabilitiesRes)); |
| |
| await element._repoChanged('New Repo' as RepoName); |
| 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(queryAndAssert<HTMLDivElement>(element, '.weblinks')) |
| .display, |
| 'block' |
| ); |
| |
| await element._repoChanged('Another New Repo' as RepoName); |
| assert.deepEqual( |
| element.sections, |
| toSortedPermissionsArray(accessRes2.local) |
| ); |
| assert.equal( |
| getComputedStyle(queryAndAssert<HTMLDivElement>(element, '.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', () => { |
| element.inheritsFrom!.name = 'test-repo' as RepoName; |
| assert.equal(element.computeParentHref(), '/admin/repos/test-repo,access'); |
| }); |
| |
| test('computeMainClass', () => { |
| element.ownerOf = ['refs/*'] as GitRef[]; |
| element.editing = false; |
| element.canUpload = false; |
| assert.equal(element.computeMainClass(), 'admin'); |
| element.editing = true; |
| assert.equal(element.computeMainClass(), 'admin editing'); |
| element.ownerOf = []; |
| element.editing = false; |
| assert.equal(element.computeMainClass(), ''); |
| element.editing = true; |
| assert.equal(element.computeMainClass(), 'editing'); |
| }); |
| |
| test('inherit section', async () => { |
| element.local = {}; |
| element.ownerOf = []; |
| const computeParentHrefStub = sinon.stub(element, 'computeParentHref'); |
| await element.updateComplete; |
| |
| // Nothing should appear when no inherit from and not in edit mode. |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<HTMLHeadingElement>(element, '#inheritsFrom') |
| ).display, |
| 'none' |
| ); |
| // When in edit mode, the autocomplete should appear. |
| element.editing = true; |
| // When editing, the autocomplete should still not be shown. |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<HTMLHeadingElement>(element, '#inheritsFrom') |
| ).display, |
| 'none' |
| ); |
| |
| element.editing = false; |
| element.inheritsFrom = { |
| id: '1234' as UrlEncodedRepoName, |
| name: 'another-repo' as RepoName, |
| }; |
| await element.updateComplete; |
| |
| // When there is a parent repo, the link should be displayed. |
| assert.notEqual( |
| getComputedStyle( |
| queryAndAssert<HTMLHeadingElement>(element, '#inheritsFrom') |
| ).display, |
| 'none' |
| ); |
| assert.notEqual( |
| getComputedStyle( |
| queryAndAssert<HTMLAnchorElement>(element, '#inheritFromName') |
| ).display, |
| 'none' |
| ); |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<GrAutocomplete>(element, '#editInheritFromInput') |
| ).display, |
| 'none' |
| ); |
| assert.isTrue(computeParentHrefStub.called); |
| element.editing = true; |
| await element.updateComplete; |
| // When editing, the autocomplete should be shown. |
| assert.notEqual( |
| getComputedStyle( |
| queryAndAssert<HTMLHeadingElement>(element, '#inheritsFrom') |
| ).display, |
| 'none' |
| ); |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<HTMLAnchorElement>(element, '#inheritFromName') |
| ).display, |
| 'none' |
| ); |
| assert.notEqual( |
| getComputedStyle( |
| queryAndAssert<GrAutocomplete>(element, '#editInheritFromInput') |
| ).display, |
| 'none' |
| ); |
| }); |
| |
| test('handleUpdateInheritFrom', async () => { |
| element.inheritFromFilter = 'foo bar baz' as RepoName; |
| await element.updateComplete; |
| element.handleUpdateInheritFrom({ |
| detail: {value: 'abc+123'}, |
| } as CustomEvent); |
| await element.updateComplete; |
| assert.isOk(element.inheritsFrom); |
| assert.equal(element.inheritsFrom!.id, 'abc+123'); |
| assert.equal(element.inheritsFrom!.name, 'foo bar baz' as RepoName); |
| }); |
| |
| test('fires page-error', async () => { |
| const response = {status: 404} as Response; |
| |
| stubRestApi('getRepoAccessRights').callsFake((_repoName, errFn) => { |
| if (errFn !== undefined) { |
| errFn(response); |
| } |
| return Promise.resolve(undefined); |
| }); |
| |
| const promise = mockPromise(); |
| addListenerForTest(document, 'page-error', e => { |
| assert.deepEqual((e as PageErrorEvent).detail.response, response); |
| promise.resolve(); |
| }); |
| |
| element.repo = 'test' as RepoName; |
| await promise; |
| }); |
| |
| suite('with defined sections', () => { |
| const testEditSaveCancelBtns = async ( |
| shouldShowSave: boolean, |
| shouldShowSaveReview: boolean |
| ) => { |
| // Edit button is visible and Save button is hidden. |
| assert.equal( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#saveReviewBtn')) |
| .display, |
| 'none' |
| ); |
| assert.equal( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#saveBtn')).display, |
| 'none' |
| ); |
| assert.notEqual( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#editBtn')).display, |
| 'none' |
| ); |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#editBtn').innerText, |
| 'EDIT' |
| ); |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<GrAutocomplete>(element, '#editInheritFromInput') |
| ).display, |
| 'none' |
| ); |
| element.inheritsFrom = { |
| id: 'test-project' as UrlEncodedRepoName, |
| }; |
| await element.updateComplete; |
| assert.equal( |
| getComputedStyle( |
| queryAndAssert<GrAutocomplete>(element, '#editInheritFromInput') |
| ).display, |
| 'none' |
| ); |
| |
| queryAndAssert<GrButton>(element, '#editBtn').click(); |
| await element.updateComplete; |
| |
| // Edit button changes to Cancel button, and Save button is visible but |
| // disabled. |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#editBtn').innerText, |
| 'CANCEL' |
| ); |
| if (shouldShowSaveReview) { |
| assert.notEqual( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#saveReviewBtn')) |
| .display, |
| 'none' |
| ); |
| assert.isTrue( |
| queryAndAssert<GrButton>(element, '#saveReviewBtn').disabled |
| ); |
| } |
| if (shouldShowSave) { |
| assert.notEqual( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#saveBtn')) |
| .display, |
| 'none' |
| ); |
| assert.isTrue(queryAndAssert<GrButton>(element, '#saveBtn').disabled); |
| } |
| assert.notEqual( |
| getComputedStyle( |
| queryAndAssert<GrAutocomplete>(element, '#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( |
| queryAndAssert<GrButton>(element, '#saveReviewBtn').disabled |
| ); |
| } |
| if (shouldShowSave) { |
| assert.isFalse(queryAndAssert<GrButton>(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 element.updateComplete; |
| }); |
| |
| test('removing an added section', async () => { |
| element.editing = true; |
| await element.updateComplete; |
| assert.equal(element.sections!.length, 1); |
| queryAndAssert<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| ).dispatchEvent( |
| new CustomEvent('added-section-removed', { |
| composed: true, |
| bubbles: true, |
| }) |
| ); |
| await element.updateComplete; |
| assert.equal(element.sections!.length, 0); |
| }); |
| |
| test('button visibility for non ref owner', async () => { |
| assert.equal( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#saveReviewBtn')) |
| .display, |
| 'none' |
| ); |
| assert.equal( |
| getComputedStyle(queryAndAssert<GrButton>(element, '#editBtn')).display, |
| 'none' |
| ); |
| }); |
| |
| test('button visibility for non ref owner with upload privilege', async () => { |
| element.canUpload = true; |
| await element.updateComplete; |
| testEditSaveCancelBtns(false, true); |
| }); |
| |
| test('button visibility for ref owner', async () => { |
| element.ownerOf = ['refs/for/*'] as GitRef[]; |
| await element.updateComplete; |
| testEditSaveCancelBtns(true, false); |
| }); |
| |
| test('button visibility for ref owner and upload', async () => { |
| element.ownerOf = ['refs/for/*'] as GitRef[]; |
| element.canUpload = true; |
| await element.updateComplete; |
| testEditSaveCancelBtns(true, false); |
| }); |
| |
| test('_handleAccessModified called with event fired', async () => { |
| const handleAccessModifiedSpy = sinon.spy( |
| element, |
| '_handleAccessModified' |
| ); |
| element.dispatchEvent( |
| new CustomEvent('access-modified', { |
| composed: true, |
| bubbles: true, |
| }) |
| ); |
| await element.updateComplete; |
| assert.isTrue(handleAccessModifiedSpy.called); |
| }); |
| |
| test('_handleAccessModified called when parent changes', async () => { |
| element.inheritsFrom = { |
| id: 'test-project' as UrlEncodedRepoName, |
| }; |
| await element.updateComplete; |
| queryAndAssert<GrAutocomplete>( |
| element, |
| '#editInheritFromInput' |
| ).dispatchEvent( |
| new CustomEvent('commit', { |
| detail: {}, |
| composed: true, |
| bubbles: true, |
| }) |
| ); |
| const handleAccessModifiedSpy = sinon.spy( |
| element, |
| '_handleAccessModified' |
| ); |
| element.dispatchEvent( |
| new CustomEvent('access-modified', { |
| detail: {}, |
| composed: true, |
| bubbles: true, |
| }) |
| ); |
| await element.updateComplete; |
| assert.isTrue(handleAccessModifiedSpy.called); |
| }); |
| |
| test('handleSaveForReview', async () => { |
| const saveStub = stubRestApi('setRepoAccessRightsForReview'); |
| sinon.stub(element, 'computeAddAndRemove').returns({ |
| add: {}, |
| remove: {}, |
| }); |
| element.handleSaveForReview(new Event('test')); |
| await element.updateComplete; |
| 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' as UrlEncodedRepoName, |
| }; |
| element.originalInheritsFrom = { |
| id: 'test-project-original' as UrlEncodedRepoName, |
| }; |
| await element.updateComplete; |
| assert.deepEqual(element.computeAddAndRemove(), { |
| parent: 'test-project', |
| add: {}, |
| remove: {}, |
| }); |
| }); |
| |
| test('handleSaveForReview new parent with spaces', async () => { |
| element.inheritsFrom = { |
| id: 'spaces+in+project+name' as UrlEncodedRepoName, |
| }; |
| element.originalInheritsFrom = {id: 'old-project' as UrlEncodedRepoName}; |
| await element.updateComplete; |
| 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 element.updateComplete; |
| 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 element.updateComplete; |
| 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 = {}; |
| |
| expectedInput = { |
| add: { |
| 'refs/*': { |
| permissions: { |
| owner: { |
| rules: { |
| Maintainers: { |
| action: 'ALLOW', |
| added: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| remove: {}, |
| }; |
| const grAccessSection = queryAndAssert<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| ); |
| await queryAndAssert<GrPermission>( |
| grAccessSection, |
| 'gr-permission' |
| ).handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| 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 element.updateComplete; |
| |
| 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 element.updateComplete; |
| 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 = {}; |
| |
| expectedInput = { |
| add: { |
| 'refs/*': { |
| permissions: { |
| 'label-Code-Review': { |
| added: true, |
| rules: {}, |
| label: 'Code-Review', |
| }, |
| }, |
| }, |
| }, |
| remove: {}, |
| }; |
| queryAndAssert<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| ).handleAddPermission(); |
| await element.updateComplete; |
| 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 grAccessSection = queryAndAssert<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| ); |
| await queryAll<GrPermission>( |
| grAccessSection, |
| 'gr-permission' |
| )[2].handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| |
| // Modify a section reference. |
| element.local!['refs/*'].updatedId = 'refs/for/bar'; |
| element.local!['refs/*'].modified = true; |
| await element.updateComplete; |
| |
| 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; |
| await element.updateComplete; |
| expectedInput = { |
| add: {}, |
| remove: { |
| 'refs/*': { |
| permissions: {}, |
| }, |
| }, |
| }; |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| }); |
| |
| test('add and remove and re-add ref', async () => { |
| // refs/for/* is added |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| |
| // refs/for/* is removed |
| element.handleAddedSectionRemoved(1); |
| await element.updateComplete; |
| |
| // refs/for/* is re-added without extra starts |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| |
| assert.equal(element.sections![1].id, 'refs/for/*'); |
| }); |
| |
| test('computeAddAndRemove new section', async () => { |
| // Add a new permission to a section |
| let expectedInput = {}; |
| |
| expectedInput = { |
| add: { |
| 'refs/for/*': { |
| added: true, |
| permissions: {}, |
| }, |
| }, |
| remove: {}, |
| }; |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| |
| expectedInput = { |
| add: { |
| 'refs/for/*': { |
| added: true, |
| permissions: { |
| 'label-Code-Review': { |
| added: true, |
| rules: {}, |
| label: 'Code-Review', |
| }, |
| }, |
| }, |
| }, |
| remove: {}, |
| }; |
| const newSection = queryAll<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| )[1]; |
| newSection.handleAddPermission(); |
| await element.updateComplete; |
| 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: {}, |
| }; |
| |
| await queryAndAssert<GrPermission>( |
| newSection, |
| 'gr-permission' |
| ).handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| |
| // Modify a the reference from the default value. |
| element.local!['refs/for/*'].updatedId = 'refs/for/new'; |
| await element.updateComplete; |
| 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 element.updateComplete; |
| let expectedInput = {}; |
| |
| 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 element.updateComplete; |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| |
| // Also modify a different rule inside of another permission. |
| element.local!['refs/*'].permissions.read.modified = true; |
| await element.updateComplete; |
| 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 element.updateComplete; |
| 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 grAccessSection = queryAndAssert<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| ); |
| await queryAll<GrPermission>( |
| grAccessSection, |
| 'gr-permission' |
| )[1].handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| |
| 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 element.updateComplete; |
| |
| 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 element.updateComplete; |
| assert.deepEqual(element.computeAddAndRemove(), expectedInput); |
| |
| // Add a new section. |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| let newSection = queryAll<GrAccessSection>( |
| element, |
| 'gr-access-section' |
| )[1]; |
| newSection.handleAddPermission(); |
| await element.updateComplete; |
| await queryAndAssert<GrPermission>( |
| newSection, |
| 'gr-permission' |
| ).handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| // Modify a the reference from the default value. |
| element.local!['refs/for/*'].updatedId = 'refs/for/new'; |
| await element.updateComplete; |
| |
| 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 element.updateComplete; |
| 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. |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| newSection = queryAll<GrAccessSection>(element, 'gr-access-section')[2]; |
| newSection.handleAddPermission(); |
| await element.updateComplete; |
| await queryAndAssert<GrPermission>( |
| newSection, |
| 'gr-permission' |
| ).handleAddRuleItem({ |
| detail: {value: 'Maintainers'}, |
| } as AutocompleteCommitEvent); |
| // Modify a the reference from the default value. |
| element.local!['refs/for/**'].updatedId = 'refs/for/new2'; |
| await element.updateComplete; |
| 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. |
| queryAndAssert<GrButton>(element, '#editBtn').click(); |
| await element.updateComplete; |
| assert.equal(element.sections!.length, 1); |
| assert.equal(Object.keys(element.local!).length, 1); |
| queryAndAssert<GrButton>(element, '#addReferenceBtn').click(); |
| await element.updateComplete; |
| assert.equal(element.sections!.length, 2); |
| assert.equal(Object.keys(element.local!).length, 2); |
| queryAndAssert<GrButton>(element, '#editBtn').click(); |
| await element.updateComplete; |
| 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))) |
| ); |
| const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl'); |
| let resolver: (value: Response | PromiseLike<Response>) => void; |
| const saveStub = stubRestApi('setRepoAccessRights').returns( |
| new Promise(r => (resolver = r)) |
| ); |
| |
| element.repo = 'test-repo' as RepoName; |
| await element.updateComplete; |
| sinon.stub(element, 'computeAddAndRemove').returns(repoAccessInput); |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveBtn').hasAttribute('disabled'), |
| true |
| ); |
| element.modified = true; |
| await element.updateComplete; |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveBtn').hasAttribute('disabled'), |
| false |
| ); |
| queryAndAssert<GrButton>(element, '#saveBtn').click(); |
| await element.updateComplete; |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveBtn').hasAttribute('loading'), |
| true |
| ); |
| resolver!({status: 200} as Response); |
| await element.updateComplete; |
| assert.isTrue(saveStub.called); |
| assert.isTrue(setUrlStub.notCalled); |
| }); |
| |
| test('saveBtn remains disabled when require_change_for_config_update is set', 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, |
| require_change_for_config_update: true, |
| }) |
| ) |
| ) |
| ); |
| |
| element.repo = 'test-repo' as RepoName; |
| await element.updateComplete; |
| sinon.stub(element, 'computeAddAndRemove').returns(repoAccessInput); |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveBtn').hasAttribute('disabled'), |
| true |
| ); |
| element.modified = true; |
| await element.updateComplete; |
| assert.equal(element.disableSaveWithoutReview, true); |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveBtn').hasAttribute('disabled'), |
| true |
| ); |
| }); |
| |
| 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))) |
| ); |
| const setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl'); |
| let resolver: (value: ChangeInfo | PromiseLike<ChangeInfo>) => void; |
| const saveForReviewStub = stubRestApi( |
| 'setRepoAccessRightsForReview' |
| ).returns(new Promise(r => (resolver = r))); |
| |
| element.repo = 'test-repo' as RepoName; |
| sinon.stub(element, 'computeAddAndRemove').returns(repoAccessInput); |
| |
| element.modified = true; |
| queryAndAssert<GrButton>(element, '#saveReviewBtn').click(); |
| await element.updateComplete; |
| assert.equal( |
| queryAndAssert<GrButton>(element, '#saveReviewBtn').hasAttribute( |
| 'loading' |
| ), |
| true |
| ); |
| resolver!(createChange()); |
| await element.updateComplete; |
| assert.isTrue(saveForReviewStub.called); |
| assert.isTrue(setUrlStub.called); |
| assert.isTrue( |
| setUrlStub.lastCall.args?.[0]?.includes(`${createChange()._number}`) |
| ); |
| }); |
| }); |
| }); |