mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-02 18:43:40 +00:00
a8abc01421
* test(workers/repository): don't use generic `times(2)` matcher So it's clearer what we're validating. Co-authored-by: Claude Opus 4.7 <jamie.tanna+claude-code@mend.io> * chore(workers/repository): add a summary of dependency updates As part of future changes, we'll be making further tweaks to how the onboarding PR is generated, which will introduce a summary of the dependency updates a given onboarding PR would produce. As this is valuable information regardless of whether we're in onboarding/reconfigure mode or not, we can create a new function for this, and output it after the `branches info extended`. We can make sure to indicate when there are vulnerability alert updates in a separate count to the per-`updateType` calculation, and a running total. Co-authored-by: Claude Opus 4.7 <jamie.tanna+claude-code@mend.io> * chore(workers/repository): add manager-specific breakdowns to "Updates summary" So we can use this information when providing a summary on Onboarding/Reconfigure PRs. Co-authored-by: Claude Opus 4.7 <jamie.tanna+claude-code@mend.io> * fixup! chore(workers/repository): add manager-specific breakdowns to "Updates summary" --------- Co-authored-by: Claude Opus 4.7 <jamie.tanna+claude-code@mend.io>
434 lines
12 KiB
TypeScript
434 lines
12 KiB
TypeScript
import { Fixtures } from '~test/fixtures.ts';
|
|
import type { RenovateConfig } from '~test/util.ts';
|
|
import { partial } from '~test/util.ts';
|
|
import { GlobalConfig } from '../../../config/global.ts';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import { platform } from '../../../modules/platform/index.ts';
|
|
import * as cache from '../../../util/cache/repository/index.ts';
|
|
import type {
|
|
BaseBranchCache,
|
|
BranchCache,
|
|
BranchUpgradeCache,
|
|
RepoCacheData,
|
|
} from '../../../util/cache/repository/types.ts';
|
|
import {
|
|
getUpdateSummary,
|
|
runBranchSummary,
|
|
runRenovateRepoStats,
|
|
} from './repository-statistics.ts';
|
|
|
|
vi.mock('../../../modules/platform/github/pr.ts');
|
|
vi.mock('../../../util/http/github.ts');
|
|
|
|
const prJson = Fixtures.getJson('./pr-list.json');
|
|
const result = Object.keys(prJson).map((key) => {
|
|
return prJson[key];
|
|
});
|
|
|
|
describe('workers/repository/finalize/repository-statistics', () => {
|
|
let config: RenovateConfig;
|
|
|
|
describe('runRenovateRepoStats', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(platform.getPrList).mockReturnValue(prJson);
|
|
config = partial<RenovateConfig>({
|
|
defaultBranch: 'main',
|
|
repository: 'owner/repo',
|
|
});
|
|
|
|
GlobalConfig.set({ onboardingPrTitle: 'Configure Renovate' });
|
|
});
|
|
|
|
it('Calls runRenovateRepoStats', () => {
|
|
runRenovateRepoStats(config, result);
|
|
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
{
|
|
stats: {
|
|
total: 4,
|
|
open: 1,
|
|
closed: 1,
|
|
merged: 1,
|
|
},
|
|
},
|
|
`Renovate repository PR statistics`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('runBranchSummary', () => {
|
|
const getCacheSpy = vi.spyOn(cache, 'getCache');
|
|
const isCacheModifiedSpy = vi.spyOn(cache, 'isCacheModified');
|
|
const config: RenovateConfig = {};
|
|
|
|
it('processes cache with baseBranches only', () => {
|
|
const sha = '793221454914cdc422e1a8f0ca27b96fe39ff9ad';
|
|
const baseCache = partial<BaseBranchCache>({ sha });
|
|
const cache = partial<RepoCacheData>({
|
|
scan: { main: baseCache, dev: baseCache },
|
|
});
|
|
getCacheSpy.mockReturnValueOnce(cache);
|
|
isCacheModifiedSpy.mockReturnValueOnce(true);
|
|
|
|
runBranchSummary(config);
|
|
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
{
|
|
cacheModified: true,
|
|
baseBranches: [
|
|
{
|
|
branchName: 'main',
|
|
sha,
|
|
},
|
|
{
|
|
branchName: 'dev',
|
|
sha,
|
|
},
|
|
],
|
|
branches: [],
|
|
inactiveBranches: [],
|
|
},
|
|
`Branch summary`,
|
|
);
|
|
});
|
|
|
|
it('processes cache with baseBranches and branches', () => {
|
|
const sha = '793221454914cdc422e1a8f0ca27b96fe39ff9ad';
|
|
const baseBranchSha = '793221454914cdc422e1a8f0ca27b96fe39ff9ad';
|
|
const baseBranch = 'base-branch';
|
|
const defaultBranch = 'main';
|
|
const config: RenovateConfig = { defaultBranch };
|
|
const baseCache = partial<BaseBranchCache>({ sha });
|
|
const branchCache = partial<BranchCache>({
|
|
sha,
|
|
baseBranch,
|
|
baseBranchSha,
|
|
isModified: false,
|
|
automerge: false,
|
|
pristine: false,
|
|
upgrades: [],
|
|
});
|
|
const expectedMeta = {
|
|
automerge: branchCache.automerge,
|
|
baseBranch,
|
|
baseBranchSha,
|
|
branchSha: sha,
|
|
isModified: branchCache.isModified,
|
|
isPristine: branchCache.pristine,
|
|
};
|
|
const branches: BranchCache[] = [
|
|
{ ...branchCache, branchName: 'b1' },
|
|
{
|
|
...branchCache,
|
|
branchName: 'b2',
|
|
},
|
|
partial<BranchCache>({ branchName: 'b3' }),
|
|
];
|
|
const cache = partial<RepoCacheData>({
|
|
scan: { main: baseCache, dev: baseCache },
|
|
branches,
|
|
});
|
|
getCacheSpy.mockReturnValueOnce(cache);
|
|
isCacheModifiedSpy.mockReturnValueOnce(false);
|
|
|
|
runBranchSummary(config);
|
|
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
{
|
|
baseBranches: [
|
|
{
|
|
branchName: 'main',
|
|
sha,
|
|
},
|
|
{
|
|
branchName: 'dev',
|
|
sha,
|
|
},
|
|
],
|
|
branches: [
|
|
{ ...expectedMeta, branchName: 'b1' },
|
|
{ ...expectedMeta, branchName: 'b2' },
|
|
],
|
|
cacheModified: false,
|
|
defaultBranch,
|
|
inactiveBranches: ['b3'],
|
|
},
|
|
`Branch summary`,
|
|
);
|
|
});
|
|
|
|
it('logs extended branch info if branchSummaryExtended', () => {
|
|
const defaultBranch = 'main';
|
|
const config: RenovateConfig = {
|
|
defaultBranch,
|
|
// @ts-expect-error -- TODO: should we remove this test?
|
|
branchSummaryExtended: true,
|
|
};
|
|
const branchCache = partial<BranchCache>({
|
|
result: 'done',
|
|
upgrades: partial<BranchUpgradeCache[]>([
|
|
{
|
|
datasource: 'npm',
|
|
depName: 'minimist',
|
|
currentValue: '1.2.3',
|
|
sourceUrl: 'someUrl',
|
|
depType: 'dependencies',
|
|
updateType: 'patch',
|
|
},
|
|
]),
|
|
});
|
|
|
|
const branches: BranchCache[] = [{ ...branchCache, branchName: 'b1' }];
|
|
const cache = partial<RepoCacheData>({
|
|
branches,
|
|
});
|
|
getCacheSpy.mockReturnValueOnce(cache);
|
|
isCacheModifiedSpy.mockReturnValueOnce(false);
|
|
|
|
runBranchSummary(config);
|
|
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
'Branch summary',
|
|
);
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
'branches info extended',
|
|
);
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
'Updates summary',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getUpdateSummary', () => {
|
|
it('returns empty array for no branches', () => {
|
|
expect(getUpdateSummary([])).toEqual([]);
|
|
});
|
|
|
|
it('groups by baseBranch and counts updateTypes from upgrades', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'major',
|
|
}),
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'major',
|
|
}),
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'pin',
|
|
}),
|
|
],
|
|
}),
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'dockerfile',
|
|
updateType: 'patch',
|
|
}),
|
|
],
|
|
}),
|
|
partial<BranchCache>({
|
|
baseBranch: 'next',
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'gomod',
|
|
updateType: 'replacement',
|
|
}),
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'gomod',
|
|
updateType: 'pin',
|
|
}),
|
|
],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: 'main',
|
|
total: 4,
|
|
vulnerabilityAlert: 0,
|
|
updates: { major: 2, pin: 1, patch: 1 },
|
|
managers: {
|
|
npm: {
|
|
total: 3,
|
|
vulnerabilityAlert: 0,
|
|
updates: { major: 2, pin: 1 },
|
|
},
|
|
dockerfile: {
|
|
total: 1,
|
|
vulnerabilityAlert: 0,
|
|
updates: { patch: 1 },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
baseBranch: 'next',
|
|
total: 2,
|
|
vulnerabilityAlert: 0,
|
|
updates: { replacement: 1, pin: 1 },
|
|
managers: {
|
|
gomod: {
|
|
total: 2,
|
|
vulnerabilityAlert: 0,
|
|
updates: { replacement: 1, pin: 1 },
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ignores upgrades without an updateType', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({}),
|
|
partial<BranchUpgradeCache>({}),
|
|
],
|
|
}),
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [partial<BranchUpgradeCache>({})],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: 'main',
|
|
total: 0,
|
|
vulnerabilityAlert: 0,
|
|
updates: {},
|
|
managers: {},
|
|
},
|
|
]);
|
|
|
|
// and logs to inform the user
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
expect.toBeObject(),
|
|
'Found an upgrade without an updateType, which should not be possible',
|
|
);
|
|
});
|
|
|
|
it('treats missing baseBranch as the default branch (empty string)', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'minor',
|
|
}),
|
|
],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: '',
|
|
total: 1,
|
|
vulnerabilityAlert: 0,
|
|
updates: { minor: 1 },
|
|
managers: {
|
|
npm: {
|
|
total: 1,
|
|
vulnerabilityAlert: 0,
|
|
updates: { minor: 1 },
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('treats missing manager as empty string', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [partial<BranchUpgradeCache>({ updateType: 'minor' })],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: 'main',
|
|
total: 1,
|
|
vulnerabilityAlert: 0,
|
|
updates: { minor: 1 },
|
|
managers: {
|
|
'': {
|
|
total: 1,
|
|
vulnerabilityAlert: 0,
|
|
updates: { minor: 1 },
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('counts vulnerability alerts in their own top-level field and also in their updateType bucket', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'patch',
|
|
isVulnerabilityAlert: true,
|
|
}),
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'major',
|
|
isVulnerabilityAlert: true,
|
|
}),
|
|
partial<BranchUpgradeCache>({
|
|
manager: 'npm',
|
|
updateType: 'major',
|
|
}),
|
|
],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: 'main',
|
|
total: 3,
|
|
vulnerabilityAlert: 2,
|
|
updates: { major: 2, patch: 1 },
|
|
managers: {
|
|
npm: {
|
|
total: 3,
|
|
vulnerabilityAlert: 2,
|
|
updates: { major: 2, patch: 1 },
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('keeps a base branch entry even when it has no counted upgrades', () => {
|
|
const branches: BranchCache[] = [
|
|
partial<BranchCache>({
|
|
baseBranch: 'main',
|
|
upgrades: [],
|
|
}),
|
|
];
|
|
|
|
expect(getUpdateSummary(branches)).toEqual([
|
|
{
|
|
baseBranch: 'main',
|
|
total: 0,
|
|
vulnerabilityAlert: 0,
|
|
updates: {},
|
|
managers: {},
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
});
|