renovate/lib/workers/branch/index.spec.ts
2021-08-17 07:57:45 +02:00

1330 lines
50 KiB
TypeScript

import * as _fs from 'fs-extra';
import {
defaultConfig,
getName,
git,
mocked,
platform,
} from '../../../test/util';
import { setGlobalConfig } from '../../config/global';
import type { RepoGlobalConfig } from '../../config/types';
import {
MANAGER_LOCKFILE_ERROR,
REPOSITORY_CHANGED,
} from '../../constants/error-messages';
import * as _npmPostExtract from '../../manager/npm/post-update';
import type { WriteExistingFilesResult } from '../../manager/npm/post-update/types';
import { PrState } from '../../types';
import * as _exec from '../../util/exec';
import { File, StatusResult } from '../../util/git';
import * as _mergeConfidence from '../../util/merge-confidence';
import * as _sanitize from '../../util/sanitize';
import * as _limits from '../global/limits';
import * as _prWorker from '../pr';
import type { EnsurePrResult } from '../pr';
import * as _prAutomerge from '../pr/automerge';
import type { Pr } from '../repository/onboarding/branch/check';
import type { BranchConfig, BranchUpgradeConfig } from '../types';
import { BranchResult } from '../types';
import * as _automerge from './automerge';
import * as _checkExisting from './check-existing';
import * as _commit from './commit';
import * as _getUpdated from './get-updated';
import type { PackageFilesResult } from './get-updated';
import * as _reuse from './reuse';
import * as _schedule from './schedule';
import * as branchWorker from '.';
jest.mock('./get-updated');
jest.mock('./schedule');
jest.mock('./check-existing');
jest.mock('./reuse');
jest.mock('../../manager/npm/post-update');
jest.mock('./automerge');
jest.mock('./commit');
jest.mock('../pr');
jest.mock('../pr/automerge');
jest.mock('../../util/exec');
jest.mock('../../util/merge-confidence');
jest.mock('../../util/sanitize');
jest.mock('../../util/git');
jest.mock('fs-extra');
jest.mock('../global/limits');
const getUpdated = mocked(_getUpdated);
const schedule = mocked(_schedule);
const checkExisting = mocked(_checkExisting);
const reuse = mocked(_reuse);
const npmPostExtract = mocked(_npmPostExtract);
const automerge = mocked(_automerge);
const commit = mocked(_commit);
const mergeConfidence = mocked(_mergeConfidence);
const prAutomerge = mocked(_prAutomerge);
const prWorker = mocked(_prWorker);
const exec = mocked(_exec);
const sanitize = mocked(_sanitize);
const fs = mocked(_fs);
const limits = mocked(_limits);
const adminConfig: RepoGlobalConfig = { localDir: '', cacheDir: '' };
describe(getName(), () => {
describe('processBranch', () => {
const updatedPackageFiles: PackageFilesResult = {
updatedPackageFiles: [],
artifactErrors: [],
updatedArtifacts: [],
};
let config: BranchConfig;
beforeEach(() => {
git.branchExists.mockReturnValue(false);
prWorker.ensurePr = jest.fn();
prAutomerge.checkAutoMerge = jest.fn();
config = {
...defaultConfig,
branchName: 'renovate/some-branch',
errors: [],
warnings: [],
upgrades: [{ depName: 'some-dep-name' }],
} as BranchConfig;
schedule.isScheduledNow.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValue('abc123');
platform.massageMarkdown.mockImplementation((prBody) => prBody);
prWorker.ensurePr.mockResolvedValue({
pr: {
title: '',
sourceBranch: '',
state: '',
body: '',
},
});
setGlobalConfig(adminConfig);
sanitize.sanitize.mockImplementation((input) => input);
});
afterEach(() => {
platform.ensureComment.mockClear();
platform.ensureCommentRemoval.mockClear();
commit.commitFilesToBranch.mockClear();
jest.resetAllMocks();
setGlobalConfig();
});
it('skips branch if not scheduled and branch does not exist', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if not scheduled and not updating out of schedule', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
config.updateNotScheduled = false;
git.branchExists.mockReturnValue(true);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch for fresh release with stabilityDays', async () => {
schedule.isScheduledNow.mockReturnValueOnce(true);
config.prCreation = 'not-pending';
config.upgrades = [
{
releaseTimestamp: new Date('2019-01-01').getTime(),
stabilityDays: 1,
},
{
releaseTimestamp: new Date().getTime(),
stabilityDays: 1,
},
/* TODO: This test is probably broken and needs to be fixed.
The type definition for "releaseTimestamp" is a string. But when I change it to
one the test starts failing. Once this test has been fixed, the never typing can be removed.
And instead replaced with the pattern used on the other places that have a config.upgrades
(#9718)
*/
] as never;
git.branchExists.mockReturnValue(false);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if not stabilityDays not met', async () => {
schedule.isScheduledNow.mockReturnValueOnce(true);
config.prCreation = 'not-pending';
(config.upgrades as Partial<BranchUpgradeConfig>[]) = [
{
releaseTimestamp: '2099-12-31',
stabilityDays: 1,
},
];
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if minimumConfidence not met', async () => {
schedule.isScheduledNow.mockReturnValueOnce(true);
config.prCreation = 'not-pending';
(config.upgrades as Partial<BranchUpgradeConfig>[]) = [
{
minimumConfidence: 'high',
},
];
mergeConfidence.isActiveConfidenceLevel.mockReturnValue(true);
mergeConfidence.satisfiesConfidenceLevel.mockReturnValueOnce(false);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('processes branch if minimumConfidence is met', async () => {
schedule.isScheduledNow.mockReturnValueOnce(true);
config.prCreation = 'not-pending';
(config.upgrades as Partial<BranchUpgradeConfig>[]) = [
{
minimumConfidence: 'high',
},
];
mergeConfidence.isActiveConfidenceLevel.mockReturnValue(true);
mergeConfidence.satisfiesConfidenceLevel.mockReturnValueOnce(true);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('processes branch if not scheduled but updating out of schedule', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
config.updateNotScheduled = true;
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Open,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(false);
await branchWorker.processBranch(config);
expect(reuse.shouldReuseExistingBranch).toHaveBeenCalled();
});
it('skips branch if closed major PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
config.updateType = 'major';
checkExisting.prAlreadyExisted.mockResolvedValueOnce({
number: 13,
state: PrState.Closed,
} as Pr);
await branchWorker.processBranch(config);
expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0);
});
it('skips branch if closed digest PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
config.updateType = 'digest';
checkExisting.prAlreadyExisted.mockResolvedValueOnce({
number: 13,
state: PrState.Closed,
} as Pr);
await branchWorker.processBranch(config);
expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0);
});
it('skips branch if closed minor PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
checkExisting.prAlreadyExisted.mockResolvedValueOnce({
number: 13,
state: PrState.Closed,
} as Pr);
await branchWorker.processBranch(config);
expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0);
});
it('skips branch if merged PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
checkExisting.prAlreadyExisted.mockResolvedValueOnce({
number: 13,
state: PrState.Merged,
} as Pr);
await branchWorker.processBranch(config);
expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0);
});
it('throws error if closed PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Merged,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
await expect(branchWorker.processBranch(config)).rejects.toThrow(
REPOSITORY_CHANGED
);
});
it('does not skip branch if edited PR found with rebaseLabel', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Open,
labels: ['rebase'],
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if edited PR found', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Open,
body: '**Rebasing**: something',
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if target branch changed', async () => {
schedule.isScheduledNow.mockReturnValueOnce(false);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Open,
targetBranch: 'v6',
} as Pr);
git.isBranchModified.mockResolvedValueOnce(false);
config.baseBranch = 'master';
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if branch edited and no PR found', async () => {
git.branchExists.mockReturnValue(true);
git.isBranchModified.mockResolvedValueOnce(true);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('continues branch if branch edited and but PR found', async () => {
git.branchExists.mockReturnValue(true);
git.isBranchModified.mockResolvedValueOnce(true);
git.getBranchCommit.mockReturnValueOnce('abc123');
platform.findPr.mockResolvedValueOnce({ sha: 'abc123' } as any);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('skips branch if branch edited and and PR found with sha mismatch', async () => {
git.branchExists.mockReturnValue(true);
git.isBranchModified.mockResolvedValueOnce(true);
git.getBranchCommit.mockReturnValueOnce('abc123');
platform.findPr.mockResolvedValueOnce({ sha: 'def456' } as any);
const res = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(res).toMatchSnapshot();
});
it('returns if branch creation limit exceeded', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
limits.isLimitReached.mockReturnValueOnce(true);
limits.isLimitReached.mockReturnValueOnce(false);
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if pr creation limit exceeded and branch exists', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
git.branchExists.mockReturnValue(true);
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'RateLimited',
});
limits.isLimitReached.mockReturnValue(false);
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if commit limit exceeded', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
git.branchExists.mockReturnValue(false);
limits.isLimitReached.mockReturnValueOnce(false);
limits.isLimitReached.mockReturnValueOnce(true);
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if no work', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
git.branchExists.mockReturnValue(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if pending checks', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
config.pendingChecks = true;
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if branch automerged', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
await branchWorker.processBranch(config);
expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
});
it('returns if branch automerged and no checks', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(false);
automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
await branchWorker.processBranch({
...config,
requiredStatusChecks: null,
});
expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
});
it('returns if branch automerged (dry-run)', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
setGlobalConfig({ ...adminConfig, dryRun: true });
await branchWorker.processBranch(config);
expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
expect(git.deleteBranch).toHaveBeenCalledTimes(0);
});
it('returns if branch exists and prCreation set to approval', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'NeedsApproval',
});
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if branch exists but pending', async () => {
expect.assertions(1);
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'AwaitingTests',
});
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if branch automerge is pending', async () => {
expect.assertions(1);
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('no automerge');
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'BranchAutomerge',
});
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if PR creation failed', async () => {
expect.assertions(1);
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'Error',
});
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('handles unknown PrBlockedBy', async () => {
expect.assertions(1);
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
prBlockedBy: 'whoops' as any,
});
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('returns if branch exists but updated', async () => {
expect.assertions(3);
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
// FIXME: explicit assert condition
expect(
await branchWorker.processBranch({
...config,
requiredStatusChecks: null,
prCreation: 'not-pending',
})
).toMatchSnapshot();
expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(0);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
});
it('ensures PR and tries automerge', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch({ ...config, automerge: true });
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1);
});
it('ensures PR when impossible to automerge', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('stale');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: false });
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch({
...config,
automerge: true,
rebaseWhen: 'conflicted',
});
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1);
});
it('ensures PR and adds lock file error comment if no releaseTimestamp', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch(config);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0);
});
it('ensures PR and adds lock file error comment if old releaseTimestamp', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
config.releaseTimestamp = '2018-04-26T05:15:51.877Z';
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch(config);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0);
});
it('ensures PR and adds lock file error comment if new releaseTimestamp and branch exists', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
config.releaseTimestamp = new Date().toISOString();
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch(config);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0);
});
it('throws error if lock file errors and new releaseTimestamp', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(false);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
config.releaseTimestamp = new Date().toISOString();
await expect(branchWorker.processBranch(config)).rejects.toThrow(
Error(MANAGER_LOCKFILE_ERROR)
);
});
it('ensures PR and adds lock file error comment recreate closed', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
config.recreateClosed = true;
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true });
commit.commitFilesToBranch.mockResolvedValueOnce(null);
await branchWorker.processBranch(config);
expect(platform.ensureComment).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(1);
expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0);
});
it('swallows branch errors', async () => {
getUpdated.getUpdatedPackageFiles.mockImplementationOnce(() => {
throw new Error('some error');
});
const processBranchResult = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(processBranchResult).toMatchSnapshot();
});
it('throws and swallows branch errors', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [{}],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
const processBranchResult = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(processBranchResult).toMatchSnapshot();
});
it('swallows pr errors', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
prWorker.ensurePr.mockImplementationOnce(() => {
throw new Error('some error');
});
const processBranchResult = await branchWorker.processBranch(config);
// FIXME: explicit assert condition
expect(processBranchResult).toMatchSnapshot();
});
it('closed pr (dry run)', async () => {
git.branchExists.mockReturnValue(true);
checkExisting.prAlreadyExisted.mockResolvedValueOnce({
state: PrState.Closed,
} as Pr);
setGlobalConfig({ ...adminConfig, dryRun: true });
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('branch pr no rebase (dry run)', async () => {
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
state: PrState.Open,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
setGlobalConfig({ ...adminConfig, dryRun: true });
// FIXME: explicit assert condition
expect(await branchWorker.processBranch(config)).toMatchSnapshot();
});
it('branch pr no schedule lockfile (dry run)', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
artifactErrors: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({ ...adminConfig, dryRun: true });
// FIXME: explicit assert condition
expect(
await branchWorker.processBranch({
...config,
updateType: 'lockFileMaintenance',
reuseExistingBranch: false,
updatedArtifacts: [{ name: '|delete|', contents: 'dummy' }],
})
).toMatchSnapshot();
});
it('branch pr no schedule (dry run)', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
artifactErrors: [{}],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
schedule.isScheduledNow.mockReturnValueOnce(false);
prWorker.ensurePr.mockResolvedValueOnce({
pr: {},
} as EnsurePrResult);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({ ...adminConfig, dryRun: true });
// FIXME: explicit assert condition
expect(
await branchWorker.processBranch({
...config,
artifactErrors: [{}],
})
).toMatchSnapshot();
});
it('branch pr no schedule', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [{}],
artifactErrors: [],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [{}],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
// FIXME: explicit assert condition
expect(
await branchWorker.processBranch({
...config,
updateType: 'lockFileMaintenance',
reuseExistingBranch: false,
updatedArtifacts: [{ name: '|delete|', contents: 'dummy' }],
})
).toMatchSnapshot();
});
it('executes post-upgrade tasks if trust is high', async () => {
const updatedPackageFile: File = {
name: 'pom.xml',
contents: 'pom.xml file contents',
};
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [updatedPackageFile],
artifactErrors: [],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [
{
name: 'yarn.lock',
contents: Buffer.from([1, 2, 3]) /* Binary content */,
},
],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['modified_file'],
not_added: [],
deleted: ['deleted_file'],
} as StatusResult);
fs.outputFile.mockReturnValue();
fs.readFile.mockResolvedValueOnce(Buffer.from('modified file content'));
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({
...adminConfig,
allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'],
allowPostUpgradeCommandTemplating: true,
exposeAllEnv: true,
localDir: '/localDir',
});
const result = await branchWorker.processBranch({
...config,
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{versioning}}}', 'disallowed task'],
fileFilters: ['modified_file', 'deleted_file'],
},
upgrades: [
{
...defaultConfig,
depName: 'some-dep-name',
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{versioning}}}', 'disallowed task'],
fileFilters: ['modified_file', 'deleted_file'],
},
} as BranchUpgradeConfig,
],
});
// FIXME: explicit assert condition
expect(result).toMatchSnapshot();
const errorMessage = expect.stringContaining(
"Post-upgrade command 'disallowed task' does not match allowed pattern '^echo {{{versioning}}}$'"
);
expect(platform.ensureComment).toHaveBeenCalledWith(
expect.objectContaining({
content: errorMessage,
})
);
expect(sanitize.sanitize).toHaveBeenCalledWith(errorMessage);
});
it('handles post-upgrade task exec errors', async () => {
const updatedPackageFile: File = {
name: 'pom.xml',
contents: 'pom.xml file contents',
};
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [updatedPackageFile],
artifactErrors: [],
} as never);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [
{
name: 'yarn.lock',
contents: Buffer.from([1, 2, 3]) /* Binary content */,
},
],
} as never);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as never);
git.isBranchModified.mockResolvedValueOnce(true);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['modified_file'],
not_added: [],
deleted: ['deleted_file'],
} as StatusResult);
fs.outputFile.mockReturnValue();
fs.readFile.mockResolvedValueOnce(Buffer.from('modified file content'));
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({
...adminConfig,
allowedPostUpgradeCommands: ['^exit 1$'],
allowPostUpgradeCommandTemplating: true,
exposeAllEnv: true,
localDir: '/localDir',
});
exec.exec.mockRejectedValue(new Error('Meh, this went wrong!'));
await branchWorker.processBranch({
...config,
upgrades: [
{
...defaultConfig,
depName: 'some-dep-name',
postUpgradeTasks: {
commands: ['exit 1'],
fileFilters: ['modified_file', 'deleted_file'],
},
} as never,
],
});
const errorMessage = expect.stringContaining('Meh, this went wrong!');
expect(platform.ensureComment).toHaveBeenCalledWith(
expect.objectContaining({
content: errorMessage,
})
);
expect(sanitize.sanitize).toHaveBeenCalledWith(errorMessage);
});
it('executes post-upgrade tasks with disabled post-upgrade command templating', async () => {
const updatedPackageFile: File = {
name: 'pom.xml',
contents: 'pom.xml file contents',
};
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [updatedPackageFile],
artifactErrors: [],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [
{
name: 'yarn.lock',
contents: Buffer.from([1, 2, 3]) /* Binary content */,
},
],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['modified_file'],
not_added: [],
deleted: ['deleted_file'],
} as StatusResult);
fs.outputFile.mockReturnValue();
fs.readFile.mockResolvedValueOnce(Buffer.from('modified file content'));
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({
...adminConfig,
allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'],
allowPostUpgradeCommandTemplating: false,
exposeAllEnv: true,
localDir: '/localDir',
});
const result = await branchWorker.processBranch({
...config,
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{versioning}}}', 'disallowed task'],
fileFilters: ['modified_file', 'deleted_file'],
},
upgrades: [
{
...defaultConfig,
depName: 'some-dep-name',
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{versioning}}}', 'disallowed task'],
fileFilters: ['modified_file', 'deleted_file'],
},
} as BranchUpgradeConfig,
],
});
// FIXME: explicit assert condition
expect(result).toMatchSnapshot();
expect(exec.exec).toHaveBeenCalledWith('echo {{{versioning}}}', {
cwd: '/localDir',
});
});
it('executes post-upgrade tasks with multiple dependecy in one branch', async () => {
const updatedPackageFile: File = {
name: 'pom.xml',
contents: 'pom.xml file contents',
};
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [updatedPackageFile],
artifactErrors: [],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [
{
name: 'yarn.lock',
contents: Buffer.from([1, 2, 3]) /* Binary content */,
},
],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
git.getRepoStatus
.mockResolvedValueOnce({
modified: ['modified_file', 'modified_then_deleted_file'],
not_added: [],
deleted: ['deleted_file', 'deleted_then_created_file'],
} as StatusResult)
.mockResolvedValueOnce({
modified: ['modified_file', 'deleted_then_created_file'],
not_added: [],
deleted: ['deleted_file', 'modified_then_deleted_file'],
} as StatusResult);
fs.outputFile.mockReturnValue();
fs.readFile
.mockResolvedValueOnce(Buffer.from('modified file content'))
.mockResolvedValueOnce(Buffer.from('this file will not exists'))
.mockResolvedValueOnce(Buffer.from('modified file content again'))
.mockResolvedValueOnce(Buffer.from('this file was once deleted'));
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({
...adminConfig,
allowedPostUpgradeCommands: ['^echo {{{depName}}}$'],
allowPostUpgradeCommandTemplating: true,
exposeAllEnv: true,
localDir: '/localDir',
});
const inconfig: BranchConfig = {
...config,
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{depName}}}', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
upgrades: [
{
...defaultConfig,
depName: 'some-dep-name-1',
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{depName}}}', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
} as BranchUpgradeConfig,
{
...defaultConfig,
depName: 'some-dep-name-2',
postUpgradeTasks: {
executionMode: 'update',
commands: ['echo {{{depName}}}', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
} as BranchUpgradeConfig,
],
};
const result = await branchWorker.processBranch(inconfig);
// FIXME: explicit assert condition
expect(result).toMatchSnapshot();
expect(exec.exec).toHaveBeenNthCalledWith(1, 'echo some-dep-name-1', {
cwd: '/localDir',
});
expect(exec.exec).toHaveBeenNthCalledWith(2, 'echo some-dep-name-2', {
cwd: '/localDir',
});
expect(exec.exec).toHaveBeenCalledTimes(2);
expect(
(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) => f.name === 'modified_file'
).contents as Buffer
).toString()
).toBe('modified file content again');
expect(
(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) => f.name === 'deleted_then_created_file'
).contents as Buffer
).toString()
).toBe('this file was once deleted');
expect(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) =>
f.contents === 'deleted_then_created_file' && f.name === '|delete|'
)
).toBeUndefined();
expect(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) => f.name === 'modified_then_deleted_file'
)
).toBeUndefined();
expect(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) =>
f.contents === 'modified_then_deleted_file' && f.name === '|delete|'
)
).not.toBeUndefined();
});
it('executes post-upgrade tasks once when set to branch mode', async () => {
const updatedPackageFile: File = {
name: 'pom.xml',
contents: 'pom.xml file contents',
};
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
updatedPackageFiles: [updatedPackageFile],
artifactErrors: [],
} as PackageFilesResult);
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [
{
name: 'yarn.lock',
contents: Buffer.from([1, 2, 3]) /* Binary content */,
},
],
} as WriteExistingFilesResult);
git.branchExists.mockReturnValue(true);
platform.getBranchPr.mockResolvedValueOnce({
title: 'rebase!',
state: PrState.Open,
body: `- [x] <!-- rebase-check -->`,
} as Pr);
git.isBranchModified.mockResolvedValueOnce(true);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['modified_file', 'modified_then_deleted_file'],
not_added: [],
deleted: ['deleted_file', 'deleted_then_created_file'],
} as StatusResult);
fs.outputFile.mockReturnValue();
fs.readFile
.mockResolvedValueOnce(Buffer.from('modified file content'))
.mockResolvedValueOnce(Buffer.from('this file will not exists'));
schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
setGlobalConfig({
...adminConfig,
allowedPostUpgradeCommands: ['^echo hardcoded-string$'],
allowPostUpgradeCommandTemplating: true,
trustLevel: 'high',
localDir: '/localDir',
});
const inconfig: BranchConfig = {
...config,
postUpgradeTasks: {
executionMode: 'branch',
commands: ['echo hardcoded-string', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
upgrades: [
{
...defaultConfig,
depName: 'some-dep-name-1',
postUpgradeTasks: {
executionMode: 'branch',
commands: ['echo hardcoded-string', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
} as BranchUpgradeConfig,
{
...defaultConfig,
depName: 'some-dep-name-2',
postUpgradeTasks: {
executionMode: 'branch',
commands: ['echo hardcoded-string', 'disallowed task'],
fileFilters: [
'modified_file',
'deleted_file',
'deleted_then_created_file',
'modified_then_deleted_file',
],
},
} as BranchUpgradeConfig,
],
};
const result = await branchWorker.processBranch(inconfig);
// FIXME: explicit assert condition
expect(result).toMatchSnapshot();
expect(exec.exec).toHaveBeenNthCalledWith(1, 'echo hardcoded-string', {
cwd: '/localDir',
});
expect(exec.exec).toHaveBeenCalledTimes(1);
expect(
(
commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find(
(f) => f.name === 'modified_file'
).contents as Buffer
).toString()
).toBe('modified file content');
});
it('returns when rebaseWhen=never', async () => {
getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
...updatedPackageFiles,
});
npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
artifactErrors: [],
updatedArtifacts: [],
});
git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null);
expect(
await branchWorker.processBranch({ ...config, rebaseWhen: 'never' })
).toMatchObject({ result: BranchResult.NoWork });
expect(commit.commitFilesToBranch).not.toHaveBeenCalled();
});
});
});