mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-02 18:43:40 +00:00
05e1271376
Enables the vitest/prefer-expect-resolves oxlint rule and applies its autofix across the spec suite.
1209 lines
37 KiB
TypeScript
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([]);
|
|
});
|
|
});
|
|
});
|