Files
renovate/lib/modules/platform/gerrit/utils.spec.ts
Dharma Teja Thoutam 40fce9aba7 fix(gerrit): resolve relative submodule URLs for nested project paths (#44667)
* fix(gerrit): resolve relative submodule URLs for nested project paths

* Fix tests after rebase

---------

Co-authored-by: Felipe Santos <felipe.santos@ericsson.com>
2026-09-03 12:41:31 +00:00

482 lines
14 KiB
TypeScript

import { hostRules } from '~test/host-rules.ts';
import { partial } from '~test/util.ts';
import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages.ts';
import type { BranchStatus } from '../../../types/index.ts';
import { setBaseUrl } from '../../../util/http/gerrit.ts';
import { hashBody } from '../pr-body.ts';
import type {
GerritAccountInfo,
GerritChange,
GerritChangeMessageInfo,
GerritLabelTypeInfo,
GerritRevisionInfo,
} from './schema.ts';
import type { GerritChangeStatus } from './types.ts';
import * as utils from './utils.ts';
import { mapBranchStatusToLabel } from './utils.ts';
const baseUrl = 'https://gerrit.example.com';
describe('modules/platform/gerrit/utils', () => {
const currentRevision = '0123456789abcdef0123456789abcdef01234567';
beforeEach(() => {
setBaseUrl(baseUrl);
});
describe('getGerritRepoUrl()', () => {
describe('no gitUrl provided', () => {
it('create a git url with username/password', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
baseUrl,
undefined,
'abc',
);
expect(repoUrl).toBe('https://abc:123@gerrit.example.com/a/web/apps');
});
it('preserves slashes for deeply nested repository paths', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'group/subgroup/my-repo',
baseUrl,
undefined,
'abc',
);
expect(repoUrl).toBe(
'https://abc:123@gerrit.example.com/a/group/subgroup/my-repo',
);
});
it('create a git url without username/password', () => {
expect(() =>
utils.getGerritRepoUrl('web/apps', baseUrl, undefined, 'abc'),
).toThrow('Init: You must configure a Gerrit Server username/password');
});
it('throws on invalid endpoint', () => {
expect(() =>
utils.getGerritRepoUrl('web/apps', '...', undefined, 'abc'),
).toThrow(Error(CONFIG_GIT_URL_UNAVAILABLE));
});
});
describe('default gitUrl', () => {
it('create a git url with username/password', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
baseUrl,
'default',
'abc',
);
expect(repoUrl).toBe('https://abc:123@gerrit.example.com/a/web/apps');
});
});
describe('endpoint gitUrl', () => {
it('create a git url with username/password', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
baseUrl,
'endpoint',
'abc',
);
expect(repoUrl).toBe('https://abc:123@gerrit.example.com/a/web/apps');
});
});
describe('ssh gitUrl', () => {
it('create a simple url', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
baseUrl,
'ssh',
'abc',
);
expect(repoUrl).toBe('ssh://abc@gerrit.example.com:29418/web/apps');
});
it('create a url with trailing slash', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
'https://gerrit.example.com/',
'ssh',
'abc',
);
expect(repoUrl).toBe('ssh://abc@gerrit.example.com:29418/web/apps');
});
it('create a url when base has context', () => {
hostRules.add({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
'https://gerrit.example.com/context',
'ssh',
'abc',
);
expect(repoUrl).toBe('ssh://abc@gerrit.example.com:29418/web/apps');
});
it('ignores non-default HTTP port from the endpoint', () => {
vi.spyOn(hostRules, 'find').mockReturnValue({
username: 'abc',
password: '123',
});
const repoUrl = utils.getGerritRepoUrl(
'web/apps',
'http://gerrit.example.com:8080/',
'ssh',
'abc',
);
expect(repoUrl).toBe('ssh://abc@gerrit.example.com:29418/web/apps');
});
});
});
describe('mapPrStateToGerritFilter()', () => {
it.each([
['closed', 'status:abandoned'],
['merged', 'status:merged'],
['!open', '-status:open'],
['open', 'status:open'],
['all', null],
[undefined, null],
])(
'maps pr state %p to gerrit filter %p',
(prState: any, filter: string | null) => {
expect(utils.mapPrStateToGerritFilter(prState)).toEqual(filter);
},
);
});
describe('mapGerritChangeStateToPrState()', () => {
it.each([
['NEW', 'open'],
['MERGED', 'merged'],
['ABANDONED', 'closed'],
['unknown', undefined],
])('maps gerrit change state %p to PrState %p', (state, prState) => {
expect(
utils.mapGerritChangeStateToPrState(state as GerritChangeStatus),
).toEqual(prState);
});
});
describe('mapGerritChangeToPr()', () => {
it('map a gerrit change to to Pr', () => {
const change = partial<GerritChange>({
_number: 123456,
status: 'NEW',
branch: 'main',
subject: 'Fix for',
created: '2025-04-14 16:33:37.000000000',
hashtags: ['hashtag1', 'hashtag2'],
reviewers: {
REVIEWER: [partial<GerritAccountInfo>({ username: 'username' })],
},
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
}),
},
messages: [
partial<GerritChangeMessageInfo>({
id: '9d78ac236714cee8c2d86e95d638358925cf6853',
tag: 'pull-request',
message: 'Patch Set 1:\n\nOld PR-Body',
}),
partial<GerritChangeMessageInfo>({
id: '1d17c930381e88e177bbc59595c3ec941bd21028',
tag: 'pull-request',
message: 'Patch Set 12:\n\nLast PR-Body',
}),
partial<GerritChangeMessageInfo>({
id: '9d78ac236714cee8c2d86e95d638358925cf6853',
message: 'other message...',
}),
],
});
expect(utils.mapGerritChangeToPr(change)).toEqual({
number: 123456,
state: 'open',
title: 'Fix for',
createdAt: '2025-04-14T16:33:37.000000000',
sourceBranch: 'renovate/dependency-1.x',
targetBranch: 'main',
labels: ['hashtag1', 'hashtag2'],
reviewers: ['username'],
bodyStruct: {
hash: hashBody('Last PR-Body'),
},
sha: currentRevision,
});
});
it('map a gerrit change without reviewers to Pr', () => {
const change = partial<GerritChange>({
_number: 123456,
status: 'NEW',
branch: 'main',
subject: 'Fix for',
reviewers: {},
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
expect(utils.mapGerritChangeToPr(change)).toEqual({
number: 123456,
state: 'open',
title: 'Fix for',
sourceBranch: 'renovate/dependency-1.x',
targetBranch: 'main',
reviewers: [],
sha: currentRevision,
bodyStruct: {
hash: hashBody(''),
},
createdAt: '2025-04-14T16:33:37.000000000',
});
});
it('does not map a gerrit change without source branch to Pr', () => {
const change = partial<GerritChange>({
_number: 123456,
status: 'NEW',
branch: 'main',
subject: 'Fix for',
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Broke: renovate/dependency-1.x\nChange-Id: ...',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
expect(utils.mapGerritChangeToPr(change)).toBeNull();
});
it('does not reject a broken commit message if knownProperties.sourceBranch is passed', () => {
const change = partial<GerritChange>({
_number: 123456,
status: 'NEW',
branch: 'main',
subject: 'Fix for',
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Broke: renovate/dependency-1.x\nChange-Id: ...',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
expect(
utils.mapGerritChangeToPr(change, {
sourceBranch: 'renovate/dependency-1.x',
}),
).toEqual({
number: 123456,
state: 'open',
title: 'Fix for',
sourceBranch: 'renovate/dependency-1.x',
targetBranch: 'main',
reviewers: [],
sha: currentRevision,
bodyStruct: {
hash: hashBody(''),
},
createdAt: '2025-04-14T16:33:37.000000000',
});
});
it('avoids iterating through change messages knownProperties.prBody is passed', () => {
const change = partial<GerritChange>({
_number: 123456,
status: 'NEW',
branch: 'main',
subject: 'Fix for',
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
}),
},
created: '2025-04-14 16:33:37.000000000',
});
expect(
utils.mapGerritChangeToPr(change, {
prBody: 'PR Body',
}),
).toEqual({
number: 123456,
state: 'open',
title: 'Fix for',
sourceBranch: 'renovate/dependency-1.x',
targetBranch: 'main',
reviewers: [],
sha: currentRevision,
bodyStruct: {
hash: hashBody('PR Body'),
},
createdAt: '2025-04-14T16:33:37.000000000',
});
});
});
describe('extractSourceBranch()', () => {
it('no commit message', () => {
const change = partial<GerritChange>();
expect(utils.extractSourceBranch(change)).toBeUndefined();
});
it('commit message with no footer', () => {
const change = partial<GerritChange>({
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers: 'some message...',
}),
},
});
expect(utils.extractSourceBranch(change)).toBeUndefined();
});
it('commit message with footer', () => {
const change = partial<GerritChange>({
current_revision: currentRevision,
revisions: {
[currentRevision]: partial<GerritRevisionInfo>({
commit_with_footers:
'Some change\n\nRenovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
}),
},
});
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-1.x');
});
});
describe('findPullRequestBody()', () => {
it('find pull-request-body', () => {
const change = partial<GerritChange>({
messages: [
partial<GerritChangeMessageInfo>({
id: '9d78ac236714cee8c2d86e95d638358925cf6853',
tag: 'pull-request',
message: 'Patch Set 1:\n\nOld PR-Body',
}),
partial<GerritChangeMessageInfo>({
id: '1d17c930381e88e177bbc59595c3ec941bd21028',
tag: 'pull-request',
message: 'Patch Set 12:\n\nLast PR-Body',
}),
partial<GerritChangeMessageInfo>({
id: '9d78ac236714cee8c2d86e95d638358925cf6853',
message: 'other message...',
}),
],
});
expect(utils.findPullRequestBody(change)).toBe('Last PR-Body');
});
it('no pull-request-body message found', () => {
const change = partial<GerritChange>({});
expect(utils.findPullRequestBody(change)).toBeUndefined();
change.messages = [];
expect(utils.findPullRequestBody(change)).toBeUndefined();
change.messages = [
partial<GerritChangeMessageInfo>({
tag: 'other-tag',
message: 'message',
}),
];
expect(utils.findPullRequestBody(change)).toBeUndefined();
});
});
describe('mapBranchStatusToLabel()', () => {
const labelWithOne: GerritLabelTypeInfo = {
values: { '-1': 'rejected', '0': 'default', '1': 'accepted' },
default_value: 0,
};
it.each([
['red', -1],
['yellow', -1],
['green', 1],
])(
'Label with +1/-1 map branchState=%p to %p',
(branchState, expectedValue) => {
expect(
mapBranchStatusToLabel(branchState as BranchStatus, labelWithOne),
).toEqual(expectedValue);
},
);
const labelWithTwo: GerritLabelTypeInfo = {
values: {
'-2': 'rejected',
'-1': 'disliked',
'0': 'default',
'1': 'looksOkay',
'2': 'approved',
},
default_value: 0,
};
it.each([
['red', -2],
['yellow', -2],
['green', 2],
])(
'Label with +2/-2, map branchState=%p to %p',
(branchState, expectedValue) => {
expect(
mapBranchStatusToLabel(branchState as BranchStatus, labelWithTwo),
).toEqual(expectedValue);
},
);
});
describe('convertGerritDateToISO()', () => {
it('converts Gerrit date format to ISO format', () => {
expect(
utils.convertGerritDateToISO('2023-05-20 14:25:30.123456789'),
).toBe('2023-05-20T14:25:30.123456789');
});
});
});