Files
renovate/lib/modules/platform/gerrit/index.spec.ts
Sebastian Poxhofer 05e1271376 test: use expect().resolves instead of awaiting inside expect (#45672)
Enables the vitest/prefer-expect-resolves oxlint rule and applies its
autofix across the spec suite.
2026-09-04 10:40:46 +00:00

1209 lines
37 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { hostRules } from '~test/host-rules.ts';
import { fakeSha, git, partial } from '~test/util.ts';
import { REPOSITORY_ARCHIVED } from '../../../constants/error-messages.ts';
import type { BranchStatus } from '../../../types/index.ts';
import { repoFingerprint } from '../util.ts';
import { client as _client } from './client.ts';
import * as gerrit from './index.ts';
import { writeToConfig } from './index.ts';
import type {
GerritAccountInfo,
GerritChange,
GerritLabelInfo,
GerritLabelTypeInfo,
GerritProjectInfo,
GerritRevisionInfo,
} from './schema.ts';
import { configureScm } from './scm.ts';
import {
REQUEST_DETAILS_FOR_PRS,
TAG_PULL_REQUEST_BODY,
mapGerritChangeToPr,
} from './utils.ts';
const gerritEndpointUrl = 'https://dev.gerrit.com/renovate';
const codeReviewLabel: GerritLabelTypeInfo = {
values: {
'-2': 'bad',
'-1': 'unlikely',
0: 'neutral',
1: 'ok',
2: 'good',
},
default_value: 0,
};
vi.mock('./client.ts');
const clientMock = vi.mocked(_client);
describe('modules/platform/gerrit/index', () => {
const currentRevision = '0123456789abcdef0123456789abcdef01234567';
beforeEach(async () => {
hostRules.add({
username: 'user',
password: 'pass',
});
writeToConfig({
repository: 'test/repo',
labels: {},
});
clientMock.getGerritVersion.mockResolvedValue('3.0.0');
await gerrit.initPlatform({
endpoint: gerritEndpointUrl,
username: 'user',
password: 'pass',
});
});
describe('initPlatform()', () => {
it('should throw if no endpoint', async () => {
expect.assertions(1);
await expect(() => gerrit.initPlatform({})).rejects.toThrow(
'Init: You must configure a Gerrit Server endpoint',
);
});
it('should throw if no username/password', async () => {
expect.assertions(1);
await expect(() =>
gerrit.initPlatform({ endpoint: 'endpoint' }),
).rejects.toThrow(
'Init: You must configure a Gerrit Server username/password',
);
});
it('should init', async () => {
await expect(
gerrit.initPlatform({
endpoint: gerritEndpointUrl,
username: 'abc',
password: '123',
}),
).resolves.toEqual({ endpoint: 'https://dev.gerrit.com/renovate/' });
});
it('should throw if auth fails', async () => {
clientMock.getGerritVersion.mockRejectedValue(new Error('Auth failed'));
await expect(
gerrit.initPlatform({
endpoint: gerritEndpointUrl,
username: 'abc',
password: '123',
}),
).rejects.toThrow('Init: Authentication failure');
});
it('should throw if version is unparseable', async () => {
clientMock.getGerritVersion.mockResolvedValue('not-a-valid-version');
await expect(
gerrit.initPlatform({
endpoint: gerritEndpointUrl,
username: 'abc',
password: '123',
}),
).rejects.toThrow('Unable to parse Gerrit version: not-a-valid-version');
});
});
describe('getRepos()', () => {
it('returns repos', async () => {
clientMock.getRepos.mockResolvedValueOnce(['repo1', 'repo2']);
await expect(gerrit.getRepos()).resolves.toEqual(['repo1', 'repo2']);
});
});
it('initRepo() - inactive', async () => {
clientMock.getProjectInfo.mockRejectedValueOnce(
new Error(REPOSITORY_ARCHIVED),
);
await expect(gerrit.initRepo({ repository: 'test/repo' })).rejects.toThrow(
REPOSITORY_ARCHIVED,
);
});
describe('initRepo()', () => {
const projectInfo: GerritProjectInfo = {
id: 'repo1',
name: 'test-repo2',
};
beforeEach(() => {
clientMock.getBranchInfo.mockResolvedValueOnce({
ref: 'sha-hash....',
revision: 'main',
});
});
it('initRepo() - active', async () => {
clientMock.getProjectInfo.mockResolvedValueOnce(projectInfo);
clientMock.findChanges.mockResolvedValueOnce([]); // rejected changes
clientMock.findChanges.mockResolvedValueOnce([]); // open changes for branch initialization
await expect(
gerrit.initRepo({ repository: 'test/repo' }),
).resolves.toEqual({
defaultBranch: 'main',
isFork: false,
repoFingerprint: repoFingerprint('test/repo', `${gerritEndpointUrl}/`),
});
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith({
url: 'https://user:pass@dev.gerrit.com/renovate/a/test/repo',
virtualBranches: {},
});
});
it('initRepo() - passes cloneSubmodules', async () => {
clientMock.getProjectInfo.mockResolvedValueOnce(projectInfo);
clientMock.findChanges.mockResolvedValueOnce([]); // rejected changes
clientMock.findChanges.mockResolvedValueOnce([]); // open changes for branch initialization
await gerrit.initRepo({
repository: 'test/repo',
cloneSubmodules: true,
cloneSubmodulesFilter: ['test'],
});
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith({
url: 'https://user:pass@dev.gerrit.com/renovate/a/test/repo',
cloneSubmodules: true,
cloneSubmodulesFilter: ['test'],
virtualBranches: {},
});
});
it('initRepo() - uses username in ssh clone url', async () => {
clientMock.getProjectInfo.mockResolvedValueOnce(projectInfo);
clientMock.findChanges.mockResolvedValueOnce([]); // rejected changes
clientMock.findChanges.mockResolvedValueOnce([]); // open changes for branch initialization
await gerrit.initRepo({
repository: 'test/repo',
gitUrl: 'ssh',
});
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith({
url: 'ssh://user@dev.gerrit.com:29418/test/repo',
virtualBranches: {},
});
});
it('initRepo() - abandon rejected changes', async () => {
clientMock.getProjectInfo.mockResolvedValueOnce({
...projectInfo,
labels: { 'Code-Review': codeReviewLabel },
});
clientMock.findChanges.mockResolvedValueOnce([
partial<GerritChange>({ _number: 1 }),
partial<GerritChange>({ _number: 2 }),
]);
clientMock.findChanges.mockResolvedValueOnce([]); // open changes for branch initialization
await gerrit.initRepo({ repository: 'test/repo' });
expect(clientMock.findChanges.mock.calls[0]).toEqual([
'test/repo',
{ branchName: '', label: '-2', state: 'open' },
]);
expect(clientMock.abandonChange.mock.calls).toEqual([
[
1,
'This change has been abandoned as it was voted with Code-Review -2.',
],
[
2,
'This change has been abandoned as it was voted with Code-Review -2.',
],
]);
});
it('initRepo() - initialize branches from open changes', async () => {
const sha1 = fakeSha('dep-1');
const sha2 = fakeSha('dep-2-skip');
const sha3 = fakeSha('dep-3');
clientMock.getProjectInfo.mockResolvedValueOnce(projectInfo);
clientMock.findChanges.mockResolvedValueOnce([]); // rejected changes
clientMock.findChanges.mockResolvedValueOnce([
partial<GerritChange>({
_number: 12345,
current_revision: sha1,
revisions: {
[sha1]: partial<GerritRevisionInfo>({
ref: 'refs/changes/45/12345/1',
commit_with_footers:
'commit message\n\nRenovate-Branch: renovate/dep-1',
}),
},
}),
// change without Renovate-Branch footer should be skipped
partial<GerritChange>({
_number: 99999,
current_revision: sha2,
revisions: {
[sha2]: partial<GerritRevisionInfo>({
ref: 'refs/changes/99/99999/1',
commit_with_footers: 'commit message without branch footer',
}),
},
}),
partial<GerritChange>({
_number: 12346,
current_revision: sha3,
revisions: {
[sha3]: partial<GerritRevisionInfo>({
ref: 'refs/changes/46/12346/1',
commit_with_footers:
'commit message\n\nRenovate-Branch: renovate/dep-2',
}),
},
}),
]);
await gerrit.initRepo({ repository: 'test/repo' });
expect(clientMock.findChanges.mock.calls[1]).toEqual([
'test/repo',
{
branchName: '',
state: 'open',
requestDetails: ['CURRENT_REVISION', 'COMMIT_FOOTERS'],
},
]);
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith({
url: 'https://user:pass@dev.gerrit.com/renovate/a/test/repo',
virtualBranches: {
'renovate/dep-1': {
ref: 'refs/changes/45/12345/1',
sha: sha1,
},
'renovate/dep-2': {
ref: 'refs/changes/46/12346/1',
sha: sha3,
},
},
});
});
});
describe('findPr()', () => {
it('findPr() - no results', async () => {
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(
gerrit.findPr({
branchName: 'branch',
state: 'open',
targetBranch: 'master',
}),
).resolves.toBeNull();
expect(clientMock.findChanges).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
branchName: 'branch',
state: 'open',
targetBranch: 'master',
singleChange: true,
requestDetails: REQUEST_DETAILS_FOR_PRS,
},
);
});
it('findPr() - found', async () => {
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: source',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.findPr({
branchName: 'branch',
state: 'open',
targetBranch: 'master',
}),
).resolves.toHaveProperty('number', 123456);
});
});
describe('getPr()', () => {
it('getPr() - found', async () => {
const change = partial<GerritChange>({
created: '2025-04-14 16:33:37.000000000',
});
clientMock.getChange.mockResolvedValueOnce(change);
await expect(gerrit.getPr(123456)).resolves.toEqual(
mapGerritChangeToPr(change),
);
expect(clientMock.getChange).toHaveBeenCalledExactlyOnceWith(
123456,
REQUEST_DETAILS_FOR_PRS,
);
});
it('getPr() - not found', async () => {
clientMock.getChange.mockRejectedValueOnce({ statusCode: 404 });
await expect(gerrit.getPr(123456)).resolves.toBeNull();
});
it('getPr() - other error', async () => {
clientMock.getChange.mockRejectedValueOnce(new Error('other error'));
await expect(gerrit.getPr(123456)).rejects.toThrow('other error');
});
});
describe('updatePr()', () => {
beforeAll(() => {
gerrit.writeToConfig({ labels: {} });
});
it('updatePr() - closed => abandon the change', async () => {
const change = partial<GerritChange>({});
clientMock.getChange.mockResolvedValueOnce(change);
await gerrit.updatePr({
number: 123456,
prTitle: change.subject,
state: 'closed',
});
expect(clientMock.abandonChange).toHaveBeenCalledExactlyOnceWith(123456);
});
it('updatePr() - body set => add as message if needed', async () => {
const change = partial<GerritChange>({});
clientMock.getChange.mockResolvedValueOnce(change);
await gerrit.updatePr({
number: 123456,
prTitle: change.subject,
prBody: 'NEW PR-Body',
});
expect(
clientMock.addMessageIfNotAlreadyExists,
).toHaveBeenCalledExactlyOnceWith(
123456,
'NEW PR-Body',
TAG_PULL_REQUEST_BODY,
);
});
it('updatePr() - with addLabels => add hashtags', async () => {
const change = partial<GerritChange>({});
clientMock.getChange.mockResolvedValueOnce(change);
await gerrit.updatePr({
number: 123456,
prTitle: change.subject,
addLabels: ['label1', 'label2'],
});
expect(clientMock.setHashtags).toHaveBeenCalledExactlyOnceWith(123456, {
add: ['label1', 'label2'],
});
});
it('updatePr() - with removeLabels => remove hashtags', async () => {
const change = partial<GerritChange>({});
clientMock.getChange.mockResolvedValueOnce(change);
await gerrit.updatePr({
number: 123456,
prTitle: change.subject,
removeLabels: ['old-label'],
});
expect(clientMock.setHashtags).toHaveBeenCalledExactlyOnceWith(123456, {
remove: ['old-label'],
});
});
it('updatePr() - with addLabels and removeLabels => update hashtags in single call', async () => {
const change = partial<GerritChange>({});
clientMock.getChange.mockResolvedValueOnce(change);
await gerrit.updatePr({
number: 123456,
prTitle: change.subject,
addLabels: ['new-label'],
removeLabels: ['old-label'],
});
expect(clientMock.setHashtags).toHaveBeenCalledExactlyOnceWith(123456, {
add: ['new-label'],
remove: ['old-label'],
});
});
});
it('updatePr() - targetBranch set => move the change', async () => {
const change = partial<GerritChange>({ branch: 'main' });
clientMock.getChange.mockResolvedValueOnce(change);
clientMock.moveChange.mockResolvedValueOnce(
partial<GerritChange>({ branch: 'new-main' }),
);
await gerrit.updatePr({
number: 123456,
prTitle: 'title',
targetBranch: 'new-main',
});
expect(clientMock.moveChange).toHaveBeenCalledExactlyOnceWith(
123456,
'new-main',
);
});
describe('createPr()', () => {
it('createPr() - creates change by pushing to refs/for/', async () => {
git.pushCommit.mockResolvedValueOnce(true);
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
ref: 'refs/changes/56/123456/1',
commit_with_footers: 'Renovate-Branch: source',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.findChanges.mockResolvedValueOnce([change]);
const pr = await gerrit.createPr({
sourceBranch: 'source',
targetBranch: 'target',
prTitle: 'title',
prBody: 'body',
});
expect(pr).toHaveProperty('number', 123456);
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'source',
targetRef: 'refs/for/target',
files: [],
pushOptions: ['notify=NONE', 'ready'],
});
expect(git.setVirtualBranch).toHaveBeenCalledExactlyOnceWith(
'source',
'refs/changes/56/123456/1',
currentRevision,
);
expect(clientMock.addMessage).toHaveBeenCalledExactlyOnceWith(
123456,
'body',
TAG_PULL_REQUEST_BODY,
);
});
it('createPr() - with autoApprove', async () => {
configureScm('test/repo', { 'Code-Review': codeReviewLabel });
git.pushCommit.mockResolvedValueOnce(true);
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
ref: 'refs/changes/56/123456/1',
commit_with_footers: 'Renovate-Branch: source',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.findChanges.mockResolvedValueOnce([change]);
const pr = await gerrit.createPr({
sourceBranch: 'source',
targetBranch: 'target',
prTitle: 'title',
prBody: 'body',
platformPrOptions: {
autoApprove: true,
},
});
expect(pr).toHaveProperty('number', 123456);
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'source',
targetRef: 'refs/for/target',
files: [],
pushOptions: ['notify=NONE', 'ready', 'label=Code-Review+2'],
});
expect(git.setVirtualBranch).toHaveBeenCalledExactlyOnceWith(
'source',
'refs/changes/56/123456/1',
currentRevision,
);
expect(clientMock.addMessage).toHaveBeenCalledExactlyOnceWith(
123456,
'body',
TAG_PULL_REQUEST_BODY,
);
});
it('createPr() - with labels', async () => {
git.pushCommit.mockResolvedValueOnce(true);
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
ref: 'refs/changes/56/123456/1',
commit_with_footers: 'Renovate-Branch: source',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.findChanges.mockResolvedValueOnce([change]);
const pr = await gerrit.createPr({
sourceBranch: 'source',
targetBranch: 'target',
prTitle: 'title',
prBody: 'body',
labels: ['label1', 'label2'],
});
expect(pr).toHaveProperty('number', 123456);
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'source',
targetRef: 'refs/for/target',
files: [],
pushOptions: [
'notify=NONE',
'ready',
'hashtag=label1',
'hashtag=label2',
],
});
expect(git.setVirtualBranch).toHaveBeenCalledExactlyOnceWith(
'source',
'refs/changes/56/123456/1',
currentRevision,
);
expect(clientMock.addMessage).toHaveBeenCalledExactlyOnceWith(
123456,
'body',
TAG_PULL_REQUEST_BODY,
);
});
it('createPr() - no change found after push => rejects', async () => {
git.pushCommit.mockResolvedValueOnce(true);
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(
gerrit.createPr({
sourceBranch: 'source',
targetBranch: 'target',
prTitle: 'title',
prBody: 'body',
}),
).rejects.toThrow(
`Could not find the Gerrit change after pushing to refs/for/target`,
);
expect(git.setVirtualBranch).not.toHaveBeenCalled();
});
it('createPr() - push fails => rejects', async () => {
git.pushCommit.mockResolvedValueOnce(false);
await expect(
gerrit.createPr({
sourceBranch: 'source',
targetBranch: 'target',
prTitle: 'title',
prBody: 'body',
}),
).rejects.toThrow(
`Failed to push commit to refs/for/target to create Gerrit change`,
);
expect(git.setVirtualBranch).not.toHaveBeenCalled();
});
});
describe('getBranchPr()', () => {
it('getBranchPr() - no result', async () => {
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(
gerrit.getBranchPr('renovate/dependency-1.x'),
).resolves.toBeNull();
expect(clientMock.getBranchChange).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
branchName: 'renovate/dependency-1.x',
state: 'open',
requestDetails: REQUEST_DETAILS_FOR_PRS,
},
);
});
it('getBranchPr() - found', async () => {
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.getBranchChange.mockResolvedValueOnce(change);
await expect(
gerrit.getBranchPr('renovate/dependency-1.x', 'master'),
).resolves.toHaveProperty('number', 123456);
expect(clientMock.getBranchChange).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
state: 'open',
branchName: 'renovate/dependency-1.x',
targetBranch: 'master',
requestDetails: REQUEST_DETAILS_FOR_PRS,
},
);
});
it('getBranchPr() - found even without targetBranch', async () => {
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.getBranchChange.mockResolvedValueOnce(change);
await expect(
gerrit.getBranchPr('renovate/dependency-1.x', undefined),
).resolves.toHaveProperty('number', 123456);
expect(clientMock.getBranchChange).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
state: 'open',
branchName: 'renovate/dependency-1.x',
targetBranch: undefined,
requestDetails: REQUEST_DETAILS_FOR_PRS,
},
);
});
});
describe('getPrList()', () => {
it('getPrList() - empty list', async () => {
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(gerrit.getPrList()).resolves.toEqual([]);
expect(clientMock.findChanges).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
branchName: '',
requestDetails: REQUEST_DETAILS_FOR_PRS,
},
);
});
it('getPrList() - multiple results', async () => {
const change = partial<GerritChange>({
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
clientMock.findChanges.mockResolvedValueOnce([change, change, change]);
await expect(gerrit.getPrList()).resolves.toHaveLength(3);
});
});
describe('mergePr()', () => {
it('mergePr() - blocker by Verified', async () => {
clientMock.submitChange.mockRejectedValueOnce({
statusCode: 409,
message: 'blocked by Verified',
});
await expect(gerrit.mergePr({ id: 123456 })).resolves.toBeFalse();
expect(clientMock.submitChange).toHaveBeenCalledExactlyOnceWith(123456);
});
it('mergePr() - success', async () => {
clientMock.submitChange.mockResolvedValueOnce(
partial<GerritChange>({ status: 'MERGED' }),
);
await expect(gerrit.mergePr({ id: 123456 })).resolves.toBeTrue();
});
it('mergePr() - other errors', async () => {
clientMock.submitChange.mockRejectedValueOnce(
new Error('any other error'),
);
await expect(gerrit.mergePr({ id: 123456 })).rejects.toThrow(
'any other error',
);
});
});
describe('getBranchStatus()', () => {
it('getBranchStatus() - change not found => yellow', async () => {
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(
gerrit.getBranchStatus('renovate/dependency-1.x'),
).resolves.toBe('yellow');
});
it('getBranchStatus() - change found, submittable and not hasProblems => green', async () => {
const change = partial<GerritChange>({
submittable: true,
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.getBranchStatus('renovate/dependency-1.x'),
).resolves.toBe('green');
});
it('getBranchStatus() - change found, submittable but hasProblems => red', async () => {
const change = partial<GerritChange>({
submittable: true,
problems: [{ message: 'error1' }, { message: 'error2' }],
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.getBranchStatus('renovate/dependency-1.x'),
).resolves.toBe('red');
});
it('getBranchStatus() - change found and hasProblems => red', async () => {
const change = partial<GerritChange>({
submittable: false,
problems: [{ message: 'error1' }, { message: 'error2' }],
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.getBranchStatus('renovate/dependency-1.x'),
).resolves.toBe('red');
});
it('getBranchStatus() - changes found and hasBlockingLabels but no problems => red', async () => {
const change = partial<GerritChange>({
submittable: true,
problems: [],
labels: {
Verified: { blocking: true },
},
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.getBranchStatus('renovate/dependency-1.x'),
).resolves.toBe('red');
});
});
describe('getBranchStatusCheck()', () => {
describe('GerritLabel is not available', () => {
beforeAll(() => {
writeToConfig({ labels: {} });
});
it.each([
'unknownCtx',
'renovate/stability-days',
'renovate/merge-confidence',
])('getBranchStatusCheck() - %s', async (ctx) => {
await expect(
gerrit.getBranchStatusCheck('renovate/dependency-1.x', ctx),
).resolves.toBe('yellow');
expect(clientMock.findChanges).not.toHaveBeenCalled();
});
});
describe('GerritLabel is available', () => {
beforeEach(() => {
writeToConfig({
labels: {
'Renovate-Merge-Confidence': {
values: { '0': 'default', '-1': 'Unsatisfied', '1': 'Satisfied' },
default_value: 0,
},
},
});
});
it.each([
{
label: 'Renovate-Merge-Confidence',
labelValue: { rejected: partial<GerritAccountInfo>({}) },
expectedState: 'red' as BranchStatus,
},
{
label: 'Renovate-Merge-Confidence',
labelValue: { approved: partial<GerritAccountInfo>({}) },
expectedState: 'green' as BranchStatus,
},
{
label: 'Renovate-Merge-Confidence',
labelValue: {
approved: partial<GerritAccountInfo>({}),
rejected: partial<GerritAccountInfo>({}),
},
expectedState: 'red' as BranchStatus,
},
{
label: 'Renovate-Merge-Confidence',
labelValue: {
approved: partial<GerritAccountInfo>({}),
rejected: partial<GerritAccountInfo>({}),
blocking: true,
},
expectedState: 'red' as BranchStatus,
},
])('$ctx/$labels', async ({ label, labelValue, expectedState }) => {
const change = partial<GerritChange>({
labels: {
[label]: partial<GerritLabelInfo>({ ...labelValue }),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.getBranchStatusCheck('renovate/dependency-1.x', label),
).resolves.toBe(expectedState);
});
});
});
describe('setBranchStatus()', () => {
describe('GerritLabel is not configured in Renovate', () => {
beforeEach(() => {
writeToConfig({ labels: {} });
});
it('setBranchStatus(renovate/stability-days)', async () => {
await expect(
gerrit.setBranchStatus({
branchName: 'branch',
context: 'renovate/stability-days',
state: 'red',
description: 'desc',
}),
).resolves.toBeUndefined();
expect(clientMock.setLabel).not.toHaveBeenCalled();
});
it('setBranchStatus(renovate/merge-confidence)', async () => {
await expect(
gerrit.setBranchStatus({
branchName: 'branch',
context: 'renovate/merge-confidence',
state: 'red',
description: 'desc',
}),
).resolves.toBeUndefined();
expect(clientMock.setLabel).not.toHaveBeenCalled();
});
});
describe('GerritLabel is configured in Renovate', () => {
beforeEach(() => {
writeToConfig({
labels: {
'Renovate-Merge-Confidence': {
values: { '0': 'default', '-1': 'Unsatisfied', '1': 'Satisfied' },
default_value: 0,
},
},
});
});
it.each([
{
ctx: 'Renovate-Merge-Confidence',
branchState: 'red' as BranchStatus,
expectedVote: -1,
expectedLabel: 'Renovate-Merge-Confidence',
},
{
ctx: 'Renovate-Merge-Confidence',
branchState: 'yellow' as BranchStatus,
expectedVote: -1,
expectedLabel: 'Renovate-Merge-Confidence',
},
{
ctx: 'Renovate-Merge-Confidence',
branchState: 'green' as BranchStatus,
expectedVote: 1,
expectedLabel: 'Renovate-Merge-Confidence',
},
])(
'$ctx/$branchState',
async ({ ctx, branchState, expectedVote, expectedLabel }) => {
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
labels: {
[ctx]: partial<GerritLabelInfo>({
approved: partial<GerritAccountInfo>({}),
rejected: partial<GerritAccountInfo>({}),
}),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await gerrit.setBranchStatus({
branchName: 'renovate/dependency-1.x',
context: ctx,
state: branchState,
description: 'desc',
});
expect(clientMock.setLabel).toHaveBeenCalledExactlyOnceWith(
123456,
expectedLabel,
expectedVote,
);
},
);
it('no change found', async () => {
clientMock.findChanges.mockResolvedValueOnce([]);
await expect(
gerrit.setBranchStatus({
branchName: 'renovate/dependency-1.x',
context: 'Renovate-Merge-Confidence',
state: 'red',
description: 'desc',
}),
).resolves.toBeUndefined();
expect(clientMock.setLabel).not.toHaveBeenCalled();
});
it('does not call setLabel() if label does not exist in change', async () => {
const change = partial<GerritChange>({
_number: 123456,
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'Renovate-Branch: renovate/dependency-1.x',
}),
},
});
clientMock.findChanges.mockResolvedValueOnce([change]);
await expect(
gerrit.setBranchStatus({
branchName: 'branch',
context: 'renovate/merge-confidence',
state: 'red',
description: 'desc',
}),
).resolves.toBeUndefined();
expect(clientMock.setLabel).not.toHaveBeenCalled();
});
});
});
describe('deleteLabel()', () => {
it('deleteLabel() - deletes a label', async () => {
const pro = gerrit.deleteLabel(123456, 'hashtag1');
await expect(pro).resolves.toBeUndefined();
expect(clientMock.setHashtags).toHaveBeenCalledExactlyOnceWith(123456, {
remove: ['hashtag1'],
});
});
});
describe('addReviewers()', () => {
it('addReviewers() - add reviewers', async () => {
await expect(
gerrit.addReviewers(123456, ['user1', 'user2']),
).resolves.toBeUndefined();
expect(clientMock.addReviewers).toHaveBeenCalledTimes(1);
expect(clientMock.addReviewers).toHaveBeenCalledExactlyOnceWith(123456, [
'user1',
'user2',
]);
});
});
describe('addAssignees()', () => {
it('addAssignees() - set assignee', async () => {
await expect(
gerrit.addAssignees(123456, ['user1', 'user2']),
).resolves.toBeUndefined();
expect(clientMock.addAssignee).toHaveBeenCalledTimes(1);
expect(clientMock.addAssignee).toHaveBeenCalledExactlyOnceWith(
123456,
'user1',
);
});
});
describe('ensureComment()', () => {
it('ensureComment() - without tag', async () => {
await expect(
gerrit.ensureComment({
number: 123456,
topic: null,
content: 'My-Comment-Msg',
}),
).resolves.toBeTrue();
expect(
clientMock.addMessageIfNotAlreadyExists,
).toHaveBeenCalledExactlyOnceWith(123456, 'My-Comment-Msg', undefined);
});
it('ensureComment() - with tag', async () => {
await expect(
gerrit.ensureComment({
number: 123456,
topic: 'myTopic',
content: 'My-Comment-Msg',
}),
).resolves.toBeTrue();
expect(
clientMock.addMessageIfNotAlreadyExists,
).toHaveBeenCalledExactlyOnceWith(123456, 'My-Comment-Msg', 'myTopic');
});
});
describe('getRawFile()', () => {
beforeEach(() => {
clientMock.getFile.mockResolvedValueOnce('{}');
});
it('getRawFile() - repo and branch', async () => {
await expect(
gerrit.getRawFile('renovate.json', 'test/repo', 'main'),
).resolves.toBe('{}');
expect(clientMock.getFile).toHaveBeenCalledExactlyOnceWith(
'test/repo',
'main',
'renovate.json',
);
});
it('getRawFile() - repo/branch from config', async () => {
writeToConfig({
repository: 'repo',
head: 'master',
labels: {},
});
await expect(gerrit.getRawFile('renovate.json')).resolves.toBe('{}');
expect(clientMock.getFile).toHaveBeenCalledExactlyOnceWith(
'repo',
'master',
'renovate.json',
);
});
it('getRawFile() - branch defaults', async () => {
writeToConfig({
repository: 'repo',
head: undefined,
labels: {},
});
await expect(gerrit.getRawFile('renovate.json')).resolves.toBe('{}');
expect(clientMock.getFile).toHaveBeenCalledExactlyOnceWith(
'repo',
'HEAD',
'renovate.json',
);
});
it('getRawFile() - no repo', async () => {
writeToConfig({
repository: undefined,
head: 'master',
labels: {},
});
await expect(gerrit.getRawFile('renovate.json')).resolves.toBe(null);
expect(clientMock.getFile).not.toHaveBeenCalled();
});
});
describe('getJsonFile()', () => {
//TODO: the wanted semantic is not clear
it('getJsonFile()', async () => {
clientMock.getFile.mockResolvedValueOnce('{}');
await expect(
gerrit.getJsonFile('renovate.json', 'test/repo', 'main'),
).resolves.toEqual({});
});
});
describe('massageMarkdown()', () => {
it('massageMarkdown()', () => {
expect(
gerrit.massageMarkdown(
codeBlock`
Pull Request
PR
Branch creation
Disabled because a matching PR was automerged previously
Whenever PR becomes conflicted
close this Pull Request unmerged
Close this PR
you tick the rebase/retry checkbox
checking the rebase/retry box above
`,
'rebase',
),
).toBe(codeBlock`
change
change
Change creation
Disabled because a matching change was automerged previously
Whenever change becomes conflicted
abandon or vote this change with Code-Review -2
Abandon or vote this change with Code-Review -2
you add the _rebase_ hashtag to this change
adding the _rebase_ hashtag to this change
`);
});
});
describe('currently unused/not-implemented functions', () => {
it('deleteLabel()', async () => {
await expect(
gerrit.deleteLabel(123456, 'label'),
).resolves.toBeUndefined();
});
it('ensureCommentRemoval()', async () => {
await expect(
gerrit.ensureCommentRemoval({
type: 'by-topic',
number: 123456,
topic: 'topic',
}),
).resolves.toBeUndefined();
});
it('ensureIssueClosing()', async () => {
await expect(gerrit.ensureIssueClosing('title')).resolves.toBeUndefined();
});
it('ensureIssue()', async () => {
await expect(
gerrit.ensureIssue({ body: 'body', title: 'title' }),
).resolves.toBeNull();
});
it('findIssue()', async () => {
await expect(gerrit.findIssue('title')).resolves.toBeNull();
});
it('getIssueList()', async () => {
await expect(gerrit.getIssueList()).resolves.toStrictEqual([]);
});
});
});