blob: d8ede818d3c013896501dc721023821e150ecd48 [file] [edit]
/**
* @license
* Copyright (C) 2024 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 {assert} from '@open-wc/testing';
import {
computeApprovalAndInfo,
getChangeAccounts,
getFileOwnership,
shouldHide,
} from './gr-files';
import {
FileOwnership,
FileStatus,
OwnerOrGroupOwner,
PatchRange,
UserRole,
} from './owners-model';
import {
AccountInfo,
ApprovalInfo,
ChangeInfo,
ChangeStatus,
DetailedLabelInfo,
SubmitRequirementResultInfo,
ReviewerState,
} from '@gerritcodereview/typescript-api/rest-api';
import {FilesOwners, OwnersLabels} from './owners-service';
import {deepEqual} from './utils';
import {getRandom} from './test-utils';
suite('owners status tests', () => {
const path = 'readme.md';
const approvedPath = 'db.sql';
const autoApprovedPath = 'foo.java';
const filesOwners = {
files: {[path]: [{name: 'John', id: 1}]},
files_approved: {[approvedPath]: [{name: 'Merry', id: 2}]},
files_auto_approved: {[autoApprovedPath]: [{name: 'Merry', id: 2}]},
} as unknown as FilesOwners;
suite('shouldHide tests', () => {
const loggedIn = getRandom(UserRole.CHANGE_OWNER, UserRole.OTHER);
test('shouldHide - should be `true` when change is not defined', () => {
const undefinedChange = undefined;
const definedPatchRange = {} as unknown as PatchRange;
assert.equal(
shouldHide(undefinedChange, definedPatchRange, filesOwners, loggedIn),
true
);
});
test('shouldHide - should be `true` when patch range is not defined', () => {
const definedChange = {} as unknown as ChangeInfo;
const undefinedPatchRange = undefined;
assert.equal(
shouldHide(definedChange, undefinedPatchRange, filesOwners, loggedIn),
true
);
});
test('shouldHide - should be `true` when change is abandoned', () => {
const abandonedChange = {
status: ChangeStatus.ABANDONED,
} as unknown as ChangeInfo;
const definedPatchRange = {} as unknown as PatchRange;
assert.equal(
shouldHide(abandonedChange, definedPatchRange, filesOwners, loggedIn),
true
);
});
test('shouldHide - should be `true` when change is merged', () => {
const mergedChange = {
status: ChangeStatus.MERGED,
} as unknown as ChangeInfo;
const definedPatchRange = {} as unknown as PatchRange;
assert.equal(
shouldHide(mergedChange, definedPatchRange, filesOwners, loggedIn),
true
);
});
test('shouldHide - should be `true` if not on the latest PS', () => {
const changeWithPs2 = {
status: ChangeStatus.NEW,
revisions: {
current_rev: {_number: 2},
},
current_revision: 'current_rev',
} as unknown as ChangeInfo;
const patchRangeOnPs1 = {patchNum: 1} as unknown as PatchRange;
assert.equal(
shouldHide(changeWithPs2, patchRangeOnPs1, filesOwners, loggedIn),
true
);
});
const change = {
status: ChangeStatus.NEW,
revisions: {
current_rev: {_number: 1},
},
current_revision: 'current_rev',
} as unknown as ChangeInfo;
const patchRange = {patchNum: 1} as unknown as PatchRange;
test('shouldHide - should be `true` when change has no submit requirements', () => {
assert.equal(shouldHide(change, patchRange, filesOwners, loggedIn), true);
});
test('shouldHide - should be `true` when change has no `Owner-Approval` submit requirements', () => {
const changeWithDifferentSubmitReqs = {
...change,
submit_requirements: [
{submittability_expression_result: {expression: 'other'}},
] as unknown as SubmitRequirementResultInfo[],
};
assert.equal(
shouldHide(
changeWithDifferentSubmitReqs,
patchRange,
filesOwners,
loggedIn
),
true
);
});
const submitRequirements = getRandom({
submit_requirements: [
{submittability_expression_result: {expression: 'has:approval_owners'}},
{
submittability_expression_result: {
expression: 'rule:owners~OwnersSubmitRequirement',
},
},
] as unknown as SubmitRequirementResultInfo[],
});
const changeWithSubmitRequirementOrRecord = {
...change,
...submitRequirements,
};
test('shouldHide - should be `true` when user is anonymous', () => {
const anonymous = UserRole.ANONYMOUS;
assert.equal(
shouldHide(
changeWithSubmitRequirementOrRecord,
patchRange,
filesOwners,
anonymous
),
true
);
});
test('shouldHide - should be `true` when files owners are `undefined`', () => {
const undefinedFilesOwners = getRandom(
undefined,
{}
) as unknown as FilesOwners;
assert.equal(
shouldHide(
changeWithSubmitRequirementOrRecord,
patchRange,
undefinedFilesOwners,
loggedIn
),
true
);
});
test('shouldHide - should be `false` when change has all files approved', () => {
const allFilesApproved = {
approved_files: {[approvedPath]: [{name: 'Merry', id: 2}]},
} as unknown as FilesOwners;
assert.equal(
shouldHide(
changeWithSubmitRequirementOrRecord,
patchRange,
allFilesApproved,
loggedIn
),
true
);
});
test('shouldHide - should be `false` when change has no files approved', () => {
const noApprovedFiles = {
files: {[path]: [{name: 'John', id: 1}]},
} as unknown as FilesOwners;
assert.equal(
shouldHide(
changeWithSubmitRequirementOrRecord,
patchRange,
noApprovedFiles,
loggedIn
),
false
);
});
test('shouldHide - should be `false` when change has both approved and not approved files', () => {
assert.equal(
shouldHide(
changeWithSubmitRequirementOrRecord,
patchRange,
filesOwners,
loggedIn
),
false
);
});
test('shouldHide - should be `false` when in edit mode', () => {
const patchRangeWithoutPatchNum = {} as unknown as PatchRange;
assert.equal(
shouldHide(change, patchRangeWithoutPatchNum, filesOwners, loggedIn),
false
);
});
});
suite('getFileOwnership tests', () => {
const emptyFilesOwners = {} as unknown as FilesOwners;
test('getFileOwnership - should be `undefined` when path is `undefined', () => {
const undefinedPath = undefined;
assert.equal(
getFileOwnership(undefinedPath, emptyFilesOwners),
undefined
);
});
test('getFileOwnership - should be `undefined` when file owners are `undefined', () => {
const undefinedFileOwners = undefined;
assert.equal(getFileOwnership(path, undefinedFileOwners), undefined);
});
test('getFileOwnership - should return `FileOwnership` with `APPROVED` fileStatus when file is approved', () => {
assert.equal(
deepEqual(getFileOwnership(approvedPath, filesOwners), {
fileStatus: FileStatus.APPROVED,
owners: [{name: 'Merry', id: 2}],
} as FileOwnership),
true
);
});
test('getFileOwnership - should return owners from `files_auto_approved` when file is auto-approved', () => {
const filesOwnersWithExplicitAndAutoApprovers = {
files_approved: {
[approvedPath]: [
{name: 'Merry', id: 2},
{name: 'John', id: 1},
],
},
files_auto_approved: {
[autoApprovedPath]: [{name: 'Merry', id: 2}],
},
} as unknown as FilesOwners;
assert.equal(
deepEqual(
getFileOwnership(
autoApprovedPath,
filesOwnersWithExplicitAndAutoApprovers
),
{
fileStatus: FileStatus.AUTO_APPROVED,
owners: [{name: 'Merry', id: 2}],
} as FileOwnership
),
true
);
});
test('getFileOwnership - should return `FileOwnership` with `NOT_OWNED` fileStatus when file has no owner', () => {
assert.equal(
deepEqual(getFileOwnership(path, emptyFilesOwners), {
fileStatus: FileStatus.NOT_OWNED,
} as FileOwnership),
true
);
});
test('getFileOwnership - should return `FileOwnership` with `NEEDS_APPROVAL` fileStatus when file has owner', () => {
const fileOwnersWithPathOwner = {
files: {[path]: [{name: 'John', id: 1}]},
} as unknown as FilesOwners;
assert.equal(
deepEqual(getFileOwnership(path, fileOwnersWithPathOwner), {
fileStatus: FileStatus.NEEDS_APPROVAL,
owners: [{name: 'John', id: 1}],
} as FileOwnership),
true
);
});
});
suite('computeApprovalAndInfo tests', () => {
const account = 1;
const fileOwner = {
owner: {_account_id: account} as unknown as AccountInfo,
} as unknown as OwnerOrGroupOwner;
const label = 'Code-Review';
const crPlus1OwnersVote = {
[`${account}`]: {[label]: 1},
} as unknown as OwnersLabels;
const changeWithLabels = {
labels: {
[label]: {
all: [
{
value: 1,
date: '2024-10-22 17:26:21.000000000',
permitted_voting_range: {
min: -2,
max: 2,
},
_account_id: account,
},
],
values: {
'-2': 'This shall not be submitted',
'-1': 'I would prefer this is not submitted as is',
' 0': 'No score',
'+1': 'Looks good to me, but someone else must approve',
'+2': 'Looks good to me, approved',
},
description: '',
default_value: 0,
},
},
} as unknown as ChangeInfo;
test('computeApprovalAndInfo - should be `undefined` when change is `undefined', () => {
const undefinedChange = undefined;
assert.equal(
computeApprovalAndInfo(fileOwner, crPlus1OwnersVote, undefinedChange),
undefined
);
});
test('computeApprovalAndInfo - should be `undefined` when there is no owners vote', () => {
const emptyOwnersVote = {};
assert.equal(
computeApprovalAndInfo(fileOwner, emptyOwnersVote, changeWithLabels),
undefined
);
});
test('computeApprovalAndInfo - should be `undefined` for default owners vote', () => {
const defaultOwnersVote = {[label]: 0} as unknown as OwnersLabels;
assert.equal(
computeApprovalAndInfo(fileOwner, defaultOwnersVote, changeWithLabels),
undefined
);
});
test('computeApprovalAndInfo - should be computed for CR+1 owners vote', () => {
const expectedApproval = {
value: 1,
date: '2024-10-22 17:26:21.000000000',
permitted_voting_range: {
min: -2,
max: 2,
},
_account_id: account,
} as unknown as ApprovalInfo;
const expectedInfo = {
all: [expectedApproval],
values: {
'-2': 'This shall not be submitted',
'-1': 'I would prefer this is not submitted as is',
' 0': 'No score',
'+1': 'Looks good to me, but someone else must approve',
'+2': 'Looks good to me, approved',
},
description: '',
default_value: 0,
} as unknown as DetailedLabelInfo;
assert.equal(
deepEqual(
computeApprovalAndInfo(
fileOwner,
crPlus1OwnersVote,
changeWithLabels
),
[expectedApproval, expectedInfo]
),
true
);
});
});
suite('getChangeAccounts tests', () => {
test('getChangeAccounts - should return empty map when change is `undefined', () => {
const undefinedChange = undefined;
const accounts = getChangeAccounts(undefinedChange);
assert.equal(accounts.byAccountId.size, 0);
assert.equal(accounts.byEmailWithoutDomain.size, 0);
assert.equal(accounts.byFullName.size, 0);
});
test('getChangeAccounts - should return map with owner when change has only owner and empty reviewers defined', () => {
const owner = account(1);
const changeWithOwner = {
owner,
reviewers: {},
} as unknown as ChangeInfo;
const accounts = getChangeAccounts(changeWithOwner);
assert.equal(
deepEqual(accounts.byAccountId, new Map([[1, owner]])),
true
);
assert.equal(
deepEqual(accounts.byEmailWithoutDomain, new Map([['1_email', owner]])),
true
);
assert.equal(
deepEqual(accounts.byFullName, new Map([['1_name', owner]])),
true
);
});
test('getChangeAccounts - should return map with owner, submitter and reviewers', () => {
const owner = account(1);
const submitter = account(2);
const reviewer = account(3);
const ccReviewer = account(4);
const change = {
owner,
submitter,
reviewers: {
[ReviewerState.REVIEWER]: [reviewer],
[ReviewerState.CC]: [ccReviewer],
},
} as unknown as ChangeInfo;
const accounts = getChangeAccounts(change);
assert.equal(
deepEqual(
accounts.byAccountId,
new Map([
[1, owner],
[2, submitter],
[3, reviewer],
[4, ccReviewer],
])
),
true
);
assert.equal(
deepEqual(
accounts.byEmailWithoutDomain,
new Map([
['1_email', owner],
['2_email', submitter],
['3_email', reviewer],
['4_email', ccReviewer],
])
),
true
);
assert.equal(
deepEqual(
accounts.byFullName,
new Map([
['1_name', owner],
['2_name', submitter],
['3_name', reviewer],
['4_name', ccReviewer],
])
),
true
);
});
});
});
function account(id: number) {
return {
_account_id: id,
email: `${id}_email@example.com`,
name: `${id}_name`,
} as unknown as AccountInfo;
}