Files
renovate/lib/modules/platform/gerrit/scm.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

391 lines
12 KiB
TypeScript

import { fakeSha, git, logger, partial } from '~test/util.ts';
import { client as _client } from './client.ts';
import type {
GerritChange,
GerritLabelTypeInfo,
GerritRevisionInfo,
} from './schema.ts';
import {
GerritScm,
configureScm,
nextPatchSetRef,
pushForReview,
} from './scm.ts';
vi.mock('./client.ts');
const clientMock = vi.mocked(_client);
describe('modules/platform/gerrit/scm', () => {
const gerritScm = new GerritScm();
const commitSha = fakeSha('commitSha');
const parentCommitSha = fakeSha('parentSha');
const currentRevision = fakeSha('abc123');
beforeEach(() => {
configureScm('test/repo');
});
describe('pushForReview()', () => {
it('pushes to refs/for/<targetBranch> and returns true on success', async () => {
git.pushCommit.mockResolvedValueOnce(true);
await expect(
pushForReview({
sourceRef: 'renovate/feat',
targetBranch: 'main',
files: [],
}),
).resolves.toBeTrue();
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'renovate/feat',
targetRef: 'refs/for/main',
files: [],
pushOptions: ['notify=NONE', 'ready'],
});
});
it('adds hashtag push options for each label', async () => {
git.pushCommit.mockResolvedValueOnce(true);
await expect(
pushForReview({
sourceRef: 'renovate/feat',
targetBranch: 'main',
files: [],
labels: ['team:backend', 'priority:high'],
}),
).resolves.toBeTrue();
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'renovate/feat',
targetRef: 'refs/for/main',
files: [],
pushOptions: [
'notify=NONE',
'ready',
'hashtag=team:backend',
'hashtag=priority:high',
],
});
});
it('skips voting and logs a warning when autoApprove is set but the project has no known Code-Review label definition', async () => {
configureScm('test/repo');
git.pushCommit.mockResolvedValueOnce(true);
await expect(
pushForReview({
sourceRef: 'renovate/feat',
targetBranch: 'main',
files: [],
autoApprove: true,
}),
).resolves.toBeTrue();
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'renovate/feat',
targetRef: 'refs/for/main',
files: [],
pushOptions: ['notify=NONE', 'ready'],
});
expect(logger.logger.warn).toHaveBeenCalledWith(
{ repository: 'test/repo', label: 'Code-Review' },
'Cannot auto-approve: label is not defined on the project',
);
});
it('uses the maximum value configured for the Code-Review label when autoApprove is set', async () => {
configureScm('test/repo', {
'Code-Review': {
values: { '-1': 'disliked', '0': 'default', '1': 'looksOkay' },
default_value: 0,
} satisfies GerritLabelTypeInfo,
});
git.pushCommit.mockResolvedValueOnce(true);
await expect(
pushForReview({
sourceRef: 'renovate/feat',
targetBranch: 'main',
files: [],
autoApprove: true,
}),
).resolves.toBeTrue();
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
sourceRef: 'renovate/feat',
targetRef: 'refs/for/main',
files: [],
pushOptions: ['notify=NONE', 'ready', 'label=Code-Review+1'],
});
});
it('returns false when push fails', async () => {
git.pushCommit.mockResolvedValueOnce(false);
await expect(
pushForReview({
sourceRef: 'renovate/feat',
targetBranch: 'main',
files: [],
}),
).resolves.toBeFalse();
});
});
describe('commitAndPush()', () => {
it('commitAndPush() - empty commit', async () => {
clientMock.getBranchChange.mockResolvedValueOnce(null);
git.prepareCommit.mockResolvedValueOnce(null); //empty commit
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'main',
message: 'commit msg',
files: [],
prTitle: 'pr title',
}),
).resolves.toBeNull();
expect(clientMock.getBranchChange).toHaveBeenCalledExactlyOnceWith(
'test/repo',
{
branchName: 'renovate/dependency-1.x',
state: 'open',
targetBranch: 'main',
requestDetails: ['CURRENT_REVISION'],
},
);
});
it('commitAndPush() - create first commit but does not push', async () => {
clientMock.getBranchChange.mockResolvedValueOnce(null);
git.prepareCommit.mockResolvedValueOnce({
commitSha,
parentCommitSha,
files: [],
});
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'main',
message: 'commit msg',
files: [],
prTitle: 'pr title',
}),
).resolves.toBe(commitSha);
expect(git.prepareCommit).toHaveBeenCalledExactlyOnceWith({
baseBranch: 'main',
branchName: 'renovate/dependency-1.x',
files: [],
message: ['pr title'],
trailers: [
'Renovate-Branch: renovate/dependency-1.x',
expect.stringMatching(/^Change-Id: I[a-z0-9]{40}$/),
],
prTitle: 'pr title',
});
// For new changes, push should NOT be called - it will be done by createPr()
expect(git.pushCommit).not.toHaveBeenCalled();
});
it('commitAndPush() - merges user trailers and overrides reserved keys', async () => {
clientMock.getBranchChange.mockResolvedValueOnce(null);
git.prepareCommit.mockResolvedValueOnce({
commitSha,
parentCommitSha,
files: [],
});
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'main',
message: 'commit msg',
files: [],
prTitle: 'pr title',
trailers: [
'Signed-off-by: Renovate Bot <bot@renovateapp.com>',
'Change-Id: Iuserprovided',
'Renovate-Branch: user-provided',
],
}),
).resolves.toBe(commitSha);
expect(git.prepareCommit).toHaveBeenCalledExactlyOnceWith({
baseBranch: 'main',
branchName: 'renovate/dependency-1.x',
files: [],
message: ['pr title'],
trailers: [
'Signed-off-by: Renovate Bot <bot@renovateapp.com>',
'Renovate-Branch: renovate/dependency-1.x',
expect.stringMatching(/^Change-Id: I[a-z0-9]{40}$/),
],
prTitle: 'pr title',
});
});
it('commitAndPush() - existing change keeps original target branch', async () => {
const existingChange = partial<GerritChange>({
change_id: 'Ifcd936eef0ced620040a07a337c586d0a882725b',
branch: 'main',
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
ref: 'refs/changes/56/123456/2',
}),
},
});
clientMock.getBranchChange.mockResolvedValueOnce(existingChange);
git.prepareCommit.mockResolvedValueOnce({
commitSha,
parentCommitSha,
files: [],
});
git.pushCommit.mockResolvedValueOnce(true);
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'new-main',
message: ['commit msg'],
files: [],
prTitle: 'pr title',
}),
).resolves.toBe(commitSha);
expect(git.prepareCommit).toHaveBeenCalledExactlyOnceWith({
baseBranch: 'new-main',
branchName: 'renovate/dependency-1.x',
files: [],
message: ['pr title'],
trailers: [
'Renovate-Branch: renovate/dependency-1.x',
'Change-Id: Ifcd936eef0ced620040a07a337c586d0a882725b',
],
prTitle: 'pr title',
});
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
files: [],
sourceRef: 'renovate/dependency-1.x',
targetRef: 'refs/for/main', // not new-main
pushOptions: ['notify=NONE', 'ready'],
});
expect(git.setVirtualBranch).toHaveBeenCalledExactlyOnceWith(
'renovate/dependency-1.x',
'refs/changes/56/123456/3',
commitSha,
);
});
it('commitAndPush() - existing change without new changes', async () => {
const existingChange = partial<GerritChange>({
change_id: 'I1bf983f8f6530c44826925b1308a45fe672408a6',
branch: 'main',
});
clientMock.getBranchChange.mockResolvedValueOnce(existingChange);
git.prepareCommit.mockResolvedValueOnce(null); //no changes
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'main',
message: ['commit msg'],
files: [],
prTitle: 'pr title',
}),
).resolves.toBeNull();
expect(git.prepareCommit).toHaveBeenCalledExactlyOnceWith({
baseBranch: 'main',
branchName: 'renovate/dependency-1.x',
files: [],
message: ['pr title'],
trailers: [
'Renovate-Branch: renovate/dependency-1.x',
'Change-Id: I1bf983f8f6530c44826925b1308a45fe672408a6',
],
prTitle: 'pr title',
});
expect(git.pushCommit).not.toHaveBeenCalled();
});
it('commitAndPush() - existing change with new changes - auto-approve', async () => {
configureScm('test/repo', {
'Code-Review': {
values: {
'-2': 'rejected',
'-1': 'disliked',
'0': 'default',
'1': 'looksOkay',
'2': 'approved',
},
default_value: 0,
} satisfies GerritLabelTypeInfo,
});
const existingChange = partial<GerritChange>({
_number: 123456,
change_id: 'I1bf983f8f6530c44826925b1308a45fe672408a6',
branch: 'main',
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
ref: 'refs/changes/56/123456/2',
}),
},
});
clientMock.getBranchChange.mockResolvedValueOnce(existingChange);
git.prepareCommit.mockResolvedValueOnce({
commitSha,
parentCommitSha,
files: [],
});
git.pushCommit.mockResolvedValueOnce(true);
await expect(
gerritScm.commitAndPush({
branchName: 'renovate/dependency-1.x',
baseBranch: 'main',
message: 'commit msg',
files: [],
prTitle: 'pr title',
autoApprove: true,
}),
).resolves.toBe(commitSha);
expect(git.prepareCommit).toHaveBeenCalledExactlyOnceWith({
baseBranch: 'main',
branchName: 'renovate/dependency-1.x',
files: [],
message: ['pr title'],
trailers: [
'Renovate-Branch: renovate/dependency-1.x',
'Change-Id: I1bf983f8f6530c44826925b1308a45fe672408a6',
],
prTitle: 'pr title',
autoApprove: true,
});
expect(git.pushCommit).toHaveBeenCalledExactlyOnceWith({
files: [],
sourceRef: 'renovate/dependency-1.x',
targetRef: 'refs/for/main',
pushOptions: ['notify=NONE', 'ready', 'label=Code-Review+2'],
});
expect(git.setVirtualBranch).toHaveBeenCalledExactlyOnceWith(
'renovate/dependency-1.x',
'refs/changes/56/123456/3',
commitSha,
);
});
});
describe('nextPatchSetRef()', () => {
it('increments patchset 1', () => {
expect(nextPatchSetRef('refs/changes/45/12345/1')).toBe(
'refs/changes/45/12345/2',
);
});
it('increments patchset 9 to 10', () => {
expect(nextPatchSetRef('refs/changes/45/12345/9')).toBe(
'refs/changes/45/12345/10',
);
});
it('increments double-digit patchset', () => {
expect(nextPatchSetRef('refs/changes/00/100/42')).toBe(
'refs/changes/00/100/43',
);
});
});
});