Files
renovate/lib/workers/repository/onboarding/pr/index.spec.ts
Sebastian Poxhofer df8c4051ff test(core): replace file snapshots with explicit assertions (#45660)
* test(core): replace file snapshots with explicit assertions

* test(core): replace inline snapshots with explicit assertions

* test(core): assert on relevant sections instead of full bodies

* test(core): drop assertions implied by full equality

* test(core): address review feedback

* test(core): use resolves instead of awaiting inside expect
2026-09-04 10:28:55 +00:00

773 lines
28 KiB
TypeScript

import type { RequestError, Response } from 'got';
import { DateTime } from 'luxon';
import type { RenovateConfig } from '~test/util.ts';
import { partial, platform, scm } from '~test/util.ts';
import { getConfig } from '../../../../config/defaults.ts';
import { GlobalConfig } from '../../../../config/global.ts';
import { InheritConfig } from '../../../../config/inherit.ts';
import { REPOSITORY_CLOSED_ONBOARDING } from '../../../../constants/error-messages.ts';
import { logger } from '../../../../logger/index.ts';
import type { PackageFile } from '../../../../modules/manager/types.ts';
import type { Pr } from '../../../../modules/platform/index.ts';
import { hashBody } from '../../../../modules/platform/pr-body.ts';
import * as memCache from '../../../../util/cache/memory/index.ts';
import type { BranchConfig, BranchUpgradeConfig } from '../../../types.ts';
import { OnboardingState } from '../common.ts';
import { ensureOnboardingPr } from './index.ts';
describe('workers/repository/onboarding/pr/index', () => {
describe('ensureOnboardingPr()', () => {
let config: RenovateConfig;
let packageFiles: Record<string, PackageFile[]>;
let branches: BranchConfig[];
// NOTE that when the test below fails, these will need to be updated
const ONBOARDING_PR_BODY_HASH_WITH_REBASE =
'27ff79952198b2914554e4f4400205617ac226bb87abc8da7ca4579e3cfd8155';
const ONBOARDING_PR_BODY_HASH_WITHOUT_REBASE =
'64fdbed75ebbbf9f5c0eaafadc9793657a6484940dcf33966fbf3f95a4622023';
// NOTE that if you're intentionally changing the onboarding PR's contents, these hashes will change - update them above
describe('generates a consistent hash of the body', () => {
it('when the rebase checkbox is present', async () => {
config.onboardingRebaseCheckbox = true;
OnboardingState.prUpdateRequested = true;
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(hashBody(prBody)).toBe(ONBOARDING_PR_BODY_HASH_WITH_REBASE);
});
it('when the rebase checkbox is not present', async () => {
config.onboardingRebaseCheckbox = false;
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(hashBody(prBody)).toBe(ONBOARDING_PR_BODY_HASH_WITHOUT_REBASE);
});
});
const bodyStruct = {
hash: ONBOARDING_PR_BODY_HASH_WITH_REBASE,
};
beforeEach(() => {
memCache.init();
config = {
...getConfig(),
errors: [],
warnings: [],
description: [],
};
packageFiles = { npm: [{ packageFile: 'package.json', deps: [] }] };
branches = [];
platform.massageMarkdown.mockImplementation((input) => input);
platform.createPr.mockResolvedValueOnce(partial<Pr>());
GlobalConfig.set({
onboardingBranch: config.onboardingBranch,
onboardingPrTitle: 'Configure Renovate', // default value
requireConfig: config.requireConfig,
});
InheritConfig.reset();
});
it('returns if onboarded cache is valid', async () => {
OnboardingState.onboardingCacheValid = true;
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(0);
expect(platform.updatePr).toHaveBeenCalledTimes(0);
});
it.each`
onboardingRebaseCheckbox | prUpdateRequested | expected
${false} | ${false} | ${1}
${false} | ${true} | ${1}
${true} | ${false} | ${0}
${true} | ${true} | ${1}
`(
'breaks early when onboarding ' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox", prUpdateRequeste="$prUpdateRequested" )',
async ({ onboardingRebaseCheckbox, prUpdateRequested, expected }) => {
config.repoIsOnboarded = false;
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
OnboardingState.prUpdateRequested = prUpdateRequested;
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.updatePr).toHaveBeenCalledTimes(0);
expect(platform.createPr).toHaveBeenCalledTimes(expected);
},
);
it('creates PR', async () => {
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
});
describe('when the PR body exceeds the platform limit', () => {
beforeEach(() => {
branches = [
partial<BranchConfig>({
prTitle: 'Update dependency foo to v2',
branchName: 'renovate/foo-2.x',
baseBranch: 'base',
manager: 'npm',
upgrades: [
partial<BranchUpgradeConfig>({
manager: 'npm',
branchName: 'renovate/foo-2.x',
updateType: 'major',
depName: 'foo',
newValue: '2.0.0',
}),
],
}),
];
});
it('replaces the full PR list and package files with their summaries', async () => {
platform.maxBodyLength.mockReturnValueOnce(1);
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith(
'Onboarding PR body exceeds platform limit, switching to summary PR list and package files',
);
const prBody = platform.createPr.mock.calls[0][0].prBody;
// the full PR list renders each branch as a `<details>` block, the summary uses a table instead
expect(prBody).not.toContain('<details>');
expect(prBody).toContain('| Manager | major |');
// the full package files description lists files inline suffixed with the manager,
// the summary groups them under a manager heading instead
expect(prBody).not.toContain('`package.json` (npm)');
expect(prBody).toContain('#### npm\n\n * `package.json`');
// in the summary view, "What to Expect" should render before "Detected Package Files"
expect(prBody.indexOf('### What to Expect')).toBeLessThan(
prBody.indexOf('### Detected Package Files'),
);
});
it('does not attempt to replace package files when none were detected', async () => {
platform.maxBodyLength.mockReturnValueOnce(1);
await expect(ensureOnboardingPr(config, {}, branches)).resolves.toBe(
'onboarding',
);
expect(platform.createPr).toHaveBeenCalledTimes(1);
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(prBody).not.toContain('Detected Package Files');
expect(prBody).toContain('| Manager | major |');
});
it('leaves the PR body untouched when it is within the platform limit', async () => {
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
expect(logger.debug).not.toHaveBeenCalledWith(
'Onboarding PR body exceeds platform limit, switching to summary PR list and package files',
);
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(prBody).toContain('<details>');
expect(prBody).toContain('`package.json` (npm)');
});
});
it('creates semantic PR', async () => {
const res = await ensureOnboardingPr(
{
...config,
semanticCommitType: undefined, // should default to "chore"
semanticCommits: 'enabled',
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
prTitle: 'chore: Configure Renovate',
}),
);
});
it('creates PR with labels', async () => {
const res = await ensureOnboardingPr(
{
...config,
labels: ['label'],
addLabels: ['label', 'additional-label'],
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
expect(platform.createPr.mock.calls[0][0].labels).toEqual([
'additional-label',
'label',
]);
});
it.each`
onboardingRebaseCheckbox
${false}
${true}
`(
'creates PR with empty footer and header' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox")',
async ({ onboardingRebaseCheckbox }) => {
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
OnboardingState.prUpdateRequested = true; // case 'false' is tested in "breaks early when onboarding"
const res = await ensureOnboardingPr(
{
...config,
prHeader: '',
prFooter: '',
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(prBody).toStartWith('\n\nWelcome to [Renovate]');
expect(prBody).toContain('### Detected Package Files');
expect(prBody).toEndWith(
'<!--renovate-config-hash:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855-->\n',
);
expect(prBody.includes('<!-- rebase-check -->')).toBe(
onboardingRebaseCheckbox,
);
},
);
it.each`
onboardingRebaseCheckbox
${false}
${true}
`(
'creates PR without footer and header when neither is configured' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox")',
async ({ onboardingRebaseCheckbox }) => {
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
OnboardingState.prUpdateRequested = true; // case 'false' is tested in "breaks early when onboarding"
const res = await ensureOnboardingPr(
{
...config,
prHeader: undefined,
prFooter: undefined,
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
const prBody = platform.createPr.mock.calls[0][0].prBody;
// no `prHeader` is prepended
expect(prBody).toMatch(/^Welcome to \[Renovate\]/);
// no `prFooter` is appended
expect(prBody).not.toContain('This PR has been generated by');
expect(prBody).toMatch(/<!--renovate-config-hash:\w+-->\n$/);
},
);
it.each`
onboardingRebaseCheckbox
${false}
${true}
`(
'creates PR with footer and header with trailing and leading newlines' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox")',
async ({ onboardingRebaseCheckbox }) => {
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
OnboardingState.prUpdateRequested = true; // case 'false' is tested in "breaks early when onboarding"
const res = await ensureOnboardingPr(
{
...config,
prHeader: '\r\r\nThis should not be the first line of the PR',
prFooter:
'There should be several empty lines at the end of the PR\r\n\n\n',
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(prBody).toMatch(
/^\s+This should not be the first line of the PR/,
);
expect(prBody).toMatch(
/There should be several empty lines at the end of the PR\s{4,}<!--renovate-config-hash:/,
);
expect(prBody.includes('<!-- rebase-check -->')).toBe(
onboardingRebaseCheckbox,
);
},
);
it.each`
onboardingRebaseCheckbox
${false}
${true}
`(
'creates PR with footer and header using templating' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox")',
async ({ onboardingRebaseCheckbox }) => {
config.baseBranch = 'some-branch';
config.repository = 'test';
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
config.onboardingConfigFileName = undefined; // checks the case when fileName isn't available
OnboardingState.prUpdateRequested = true; // case 'false' is tested in "breaks early when onboarding"
const res = await ensureOnboardingPr(
{
...config,
prHeader: 'This is a header for platform:{{platform}}',
prFooter:
'And this is a footer for repository:{{repository}} baseBranch:{{baseBranch}}',
},
packageFiles,
branches,
);
expect(res).toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
expect(platform.createPr.mock.calls[0][0].prBody).toMatch(
/platform:github/,
);
expect(platform.createPr.mock.calls[0][0].prBody).toMatch(
/repository:test/,
);
const prBody = platform.createPr.mock.calls[0][0].prBody;
expect(prBody).toStartWith('This is a header for platform:github\n');
expect(prBody).toContain(
'And this is a footer for repository:test baseBranch:some-branch',
);
expect(prBody.includes('<!-- rebase-check -->')).toBe(
onboardingRebaseCheckbox,
);
},
);
it.each`
onboardingRebaseCheckbox
${false}
${true}
`(
'returns if PR does not need updating' +
'(onboardingRebaseCheckbox="$onboardingRebaseCheckbox")',
async ({ onboardingRebaseCheckbox }) => {
const hash = ONBOARDING_PR_BODY_HASH_WITHOUT_REBASE;
config.onboardingRebaseCheckbox = onboardingRebaseCheckbox;
OnboardingState.prUpdateRequested = true; // case 'false' is tested in "breaks early when onboarding"
platform.getBranchPr.mockResolvedValue(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct: onboardingRebaseCheckbox ? bodyStruct : { hash },
}),
);
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(0);
expect(platform.updatePr).toHaveBeenCalledTimes(0);
},
);
it('ensures comment, when PR is conflicted', async () => {
config.baseBranch = 'some-branch';
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
}),
);
scm.isBranchConflicted.mockResolvedValueOnce(true);
await expect(ensureOnboardingPr(config, {}, branches)).resolves.toBe(
'onboarding',
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.createPr).toHaveBeenCalledTimes(0);
expect(platform.updatePr).toHaveBeenCalledTimes(0);
});
describe('when onboardingAutoCloseAge is set', () => {
beforeAll(() => {
vi.useFakeTimers();
});
it('ensures comment, if onboarding cache is up-to-date, but when onboarding pr is over onboardingAutoCloseAge', async () => {
OnboardingState.onboardingCacheValid = true;
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
const createdAt = now.minus({ hour: 48 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
it('does not comment, when onboarding pr is exactly at onboardingAutoCloseAge', async () => {
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
// at exactly 1 day ago, which means that an `onboardingAutoCloseAge=1` SHOULD NOT trigger, as it's > 1
const createdAt = now.minus({ hour: 24 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).resolves.toBe(
'onboarding',
);
expect(platform.ensureComment).toHaveBeenCalledTimes(0);
expect(platform.createPr).toHaveBeenCalledTimes(0);
});
it('ensures comment, when onboarding pr is partially over onboardingAutoCloseAge', async () => {
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
// we're currently 1 day and 1 second ahead of the creation time, which is 1.x days since the PR was created, which means that an `onboardingAutoCloseAge=1` should trigger, as it's > 1
const createdAt = now.minus({ hour: 24, seconds: 1 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
it('ensures comment, when onboarding pr is 1 day older than onboardingAutoCloseAge', async () => {
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
// we're currently 25 hours ahead of the creation time, which is 1.x days since the PR was created, which means that an `onboardingAutoCloseAge=1` should trigger, as it's > 1
const createdAt = now.minus({ hour: 48 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
it('ensures comment,when onboarding pr is significantly older than onboardingAutoCloseAge', async () => {
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: '2020-02-29T01:40:21Z',
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
it('prefers inherited onboardingAutoCloseAge over global config', async () => {
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
// PR was created 36 hours ago (1.5 days)
const createdAt = now.minus({ hour: 36 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 2 });
InheritConfig.set({ onboardingAutoCloseAge: 1 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
it('does not allow inherited onboardingAutoCloseAge to be higher than global config', async () => {
const now = DateTime.now();
vi.setSystemTime(now.toMillis());
// PR was created 36 hours ago (1.5 days)
const createdAt = now.minus({ hour: 36 });
config.baseBranch = 'some-branch';
GlobalConfig.set({ onboardingAutoCloseAge: 1 });
InheritConfig.set({ onboardingAutoCloseAge: 10 });
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
createdAt: createdAt.toISO(),
number: 1,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).rejects.toThrow(
REPOSITORY_CLOSED_ONBOARDING,
);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(platform.updatePr).toHaveBeenCalledWith({
number: 1,
state: 'closed',
prTitle: 'Configure Renovate',
});
});
});
it('does nothing in dry run when PR is conflicted', async () => {
GlobalConfig.set({ dryRun: 'full' });
config.baseBranch = 'some-branch';
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
}),
);
scm.isBranchConflicted.mockResolvedValueOnce(true);
await expect(ensureOnboardingPr(config, {}, branches)).resolves.toBe(
'onboarding',
);
expect(logger.info).toHaveBeenLastCalledWith(
'DRY-RUN: Would comment that Onboarding PR is conflicted and needs manual resolving',
);
expect(platform.ensureComment).toHaveBeenCalledTimes(0);
expect(platform.createPr).toHaveBeenCalledTimes(0);
expect(platform.updatePr).toHaveBeenCalledTimes(0);
});
it('updates PR when modified', async () => {
config.baseBranch = 'some-branch';
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
}),
);
await expect(ensureOnboardingPr(config, {}, branches)).resolves.toBe(
'onboarding',
);
expect(platform.createPr).toHaveBeenCalledTimes(0);
expect(platform.updatePr).toHaveBeenCalledTimes(1);
});
it('creates PR (no require config)', async () => {
config.requireConfig = 'optional';
GlobalConfig.set({
onboardingBranch: config.onboardingBranch,
onboardingPrTitle: 'Configure Renovate',
requireConfig: 'optional',
});
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
});
it('creates PR (require config)', async () => {
config.requireConfig = 'required';
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr).toHaveBeenCalledTimes(1);
});
describe('the created PR references onboardingConfigFileName', () => {
it('when set', async () => {
GlobalConfig.set({ onboardingConfigFileName: '.github/renovate.json' });
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr.mock.calls[0][0].prBody).toContain(
`Add your custom config to \`.github/renovate.json\` in this branch`,
);
expect(platform.createPr.mock.calls[0][0].prBody).not.toContain(
'`renovate.json`',
);
});
it('when not set, falls back to "renovate.json"', async () => {
GlobalConfig.set({ onboardingConfigFileName: undefined });
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr.mock.calls[0][0].prBody).toContain(
`Add your custom config to \`renovate.json\` in this branch`,
);
});
it('when set, but not a valid filename, falls back to "renovate.json"', async () => {
GlobalConfig.set({ onboardingConfigFileName: 'foo.bar' });
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(platform.createPr.mock.calls[0][0].prBody).toContain(
`Add your custom config to \`renovate.json\` in this branch`,
);
});
});
it('dryrun of creates PR', async () => {
GlobalConfig.set({
dryRun: 'full',
onboardingBranch: config.onboardingBranch,
});
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(logger.info).toHaveBeenCalledWith(
'DRY-RUN: Would check branch renovate/configure',
);
expect(logger.info).toHaveBeenLastCalledWith(
'DRY-RUN: Would create onboarding PR',
);
});
it('dryrun of updates PR', async () => {
GlobalConfig.set({
dryRun: 'full',
onboardingBranch: config.onboardingBranch,
});
platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({
title: 'Configure Renovate',
bodyStruct,
}),
);
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(logger.info).toHaveBeenCalledWith(
'DRY-RUN: Would check branch renovate/configure',
);
expect(logger.info).toHaveBeenLastCalledWith(
'DRY-RUN: Would update onboarding PR',
);
});
describe('ensureOnboardingPr() throws', () => {
const response = partial<Response>({ statusCode: 422 });
const err = partial<RequestError>({ response });
beforeEach(() => {
GlobalConfig.reset();
scm.deleteBranch.mockResolvedValue();
// oxlint-disable-next-line renovate/no-redundant-mock-reset -- discards the once-value queued by the outer beforeEach
platform.createPr.mockReset();
});
it('throws when trying to create a new PR', async () => {
platform.createPr.mockRejectedValueOnce(err);
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).toReject();
expect(scm.deleteBranch).toHaveBeenCalledTimes(0);
});
it('deletes branch when PR already exists but cannot find it', async () => {
response.body = {
errors: [{ message: 'A pull request already exists' }],
};
platform.createPr.mockRejectedValueOnce(err);
await expect(
ensureOnboardingPr(config, packageFiles, branches),
).resolves.toBe('onboarding');
expect(logger.warn).toHaveBeenCalledWith(
'Onboarding PR already exists but cannot find it. It was probably created by a different user.',
);
expect(scm.deleteBranch).toHaveBeenCalledTimes(1);
});
});
});
});