import fs from 'fs'; import { mock } from 'jest-mock-extended'; import { PR_STATE_NOT_OPEN } from '../../constants/pull-requests'; import * as masterIssue from './master-issue'; import { RenovateConfig, getConfig, platform } from '../../../test/util'; import { BranchConfig, BranchUpgradeConfig } from '../common'; import { Pr } from '../../platform'; import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms'; type PrUpgrade = BranchUpgradeConfig; let config: RenovateConfig; beforeEach(() => { jest.resetAllMocks(); config = getConfig(); config.platform = PLATFORM_TYPE_GITHUB; config.errors = []; config.warnings = []; }); async function dryRun( branches: BranchConfig[], // eslint-disable-next-line no-shadow platform, ensureIssueClosingCalls = 0, ensureIssueCalls = 0, getBranchPrCalls = 0, findPrCalls = 0 ) { jest.resetAllMocks(); config.dryRun = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes( ensureIssueClosingCalls ); expect(platform.ensureIssue).toHaveBeenCalledTimes(ensureIssueCalls); expect(platform.getBranchPr).toHaveBeenCalledTimes(getBranchPrCalls); expect(platform.findPr).toHaveBeenCalledTimes(findPrCalls); } describe('workers/repository/master-issue', () => { describe('ensureMasterIssue()', () => { it('do nothing if masterissue is disable', async () => { const branches: BranchConfig[] = []; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('do nothing if it has no masterissueapproval branches', async () => { const branches = [ { ...mock<BranchConfig>(), prTitle: 'pr1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', masterIssueApproval: false, }, ]; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('closes master issue when there is 0 PR opened and masterIssueAutoclose is true', async () => { const branches: BranchConfig[] = []; config.masterIssue = true; config.masterIssueAutoclose = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(1); expect(platform.ensureIssueClosing.mock.calls[0][0]).toBe( config.masterIssueTitle ); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('closes master issue when all branches are automerged and masterIssueAutoclose is true', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', res: 'automerged' }, { ...mock<BranchConfig>(), prTitle: 'pr2', res: 'automerged', masterIssueApproval: false, }, ]; config.masterIssue = true; config.masterIssueAutoclose = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(1); expect(platform.ensureIssueClosing.mock.calls[0][0]).toBe( config.masterIssueTitle ); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('open or update master issue when all branches are closed and masterIssueAutoclose is false', async () => { const branches: BranchConfig[] = []; config.masterIssue = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( 'This repository is up-to-date and has no outstanding updates open or pending.' ); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('checks an issue with 2 Pending Approvals, 2 not scheduled, 2 pr-hourly-limit-reached and 2 in error', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', upgrades: [{ ...mock<BranchUpgradeConfig>(), depName: 'dep1' }], res: 'needs-approval', branchName: 'branchName1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep2' }], res: 'needs-approval', branchName: 'branchName2', }, { ...mock<BranchConfig>(), prTitle: 'pr3', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep3' }], res: 'not-scheduled', branchName: 'branchName3', }, { ...mock<BranchConfig>(), prTitle: 'pr4', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep4' }], res: 'not-scheduled', branchName: 'branchName4', }, { ...mock<BranchConfig>(), prTitle: 'pr5', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep5' }], res: 'pr-hourly-limit-reached', branchName: 'branchName5', }, { ...mock<BranchConfig>(), prTitle: 'pr6', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep6' }], res: 'pr-hourly-limit-reached', branchName: 'branchName6', }, { ...mock<BranchConfig>(), prTitle: 'pr7', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep7' }], res: 'error', branchName: 'branchName7', }, { ...mock<BranchConfig>(), prTitle: 'pr8', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep8' }], res: 'error', branchName: 'branchName8', }, ]; config.masterIssue = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( 'lib/workers/repository/__fixtures__/master-issue_with_8_PR.txt', 'utf8' ) ); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); it('checks an issue with 2 PR pr-edited', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep1' }], res: 'pr-edited', branchName: 'branchName1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', upgrades: [ { ...mock<PrUpgrade>(), depName: 'dep2' }, { ...mock<PrUpgrade>(), depName: 'dep3' }, ], res: 'pr-edited', branchName: 'branchName2', }, ]; config.masterIssue = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined); await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( 'lib/workers/repository/__fixtures__/master-issue_with_2_PR_edited.txt', 'utf8' ) ); expect(platform.getBranchPr).toHaveBeenCalledTimes(2); expect(platform.getBranchPr.mock.calls[0][0]).toBe('branchName1'); expect(platform.getBranchPr.mock.calls[1][0]).toBe('branchName2'); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform, 0, 0, 2, 0); }); it('checks an issue with 3 PR in progress and rebase all option', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep1' }], res: 'rebase', branchName: 'branchName1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', upgrades: [ { ...mock<PrUpgrade>(), depName: 'dep2' }, { ...mock<PrUpgrade>(), depName: 'dep3' }, ], res: 'rebase', branchName: 'branchName2', }, { ...mock<BranchConfig>(), prTitle: 'pr3', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep3' }], res: 'rebase', branchName: 'branchName3', }, ]; config.masterIssue = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce({ ...mock<Pr>(), number: 3 }); await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( 'lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_progress.txt', 'utf8' ) ); expect(platform.getBranchPr).toHaveBeenCalledTimes(3); expect(platform.getBranchPr.mock.calls[0][0]).toBe('branchName1'); expect(platform.getBranchPr.mock.calls[1][0]).toBe('branchName2'); expect(platform.getBranchPr.mock.calls[2][0]).toBe('branchName3'); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform, 0, 0, 3, 0); }); it('checks an issue with 2 PR closed / ignored', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep1' }], res: 'already-existed', branchName: 'branchName1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', upgrades: [ { ...mock<PrUpgrade>(), depName: 'dep2' }, { ...mock<PrUpgrade>(), depName: 'dep3' }, ], res: 'already-existed', branchName: 'branchName2', }, ]; config.masterIssue = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce({ ...mock<Pr>(), number: 3 }); await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( 'lib/workers/repository/__fixtures__/master-issue_with_2_PR_closed_ignored.txt', 'utf8' ) ); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); expect(platform.findPr).toHaveBeenCalledTimes(2); expect(platform.findPr.mock.calls[0][0].branchName).toBe('branchName1'); expect(platform.findPr.mock.calls[0][0].prTitle).toBe('pr1'); expect(platform.findPr.mock.calls[0][0].state).toBe(PR_STATE_NOT_OPEN); expect(platform.findPr.mock.calls[1][0].branchName).toBe('branchName2'); expect(platform.findPr.mock.calls[1][0].prTitle).toBe('pr2'); expect(platform.findPr.mock.calls[1][0].state).toBe(PR_STATE_NOT_OPEN); // same with dry run await dryRun(branches, platform, 0, 0, 0, 2); }); it('checks an issue with 3 PR in approval', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep1' }], res: 'needs-pr-approval', branchName: 'branchName1', }, { ...mock<BranchConfig>(), prTitle: 'pr2', upgrades: [ { ...mock<PrUpgrade>(), depName: 'dep2' }, { ...mock<PrUpgrade>(), depName: 'dep3' }, ], res: 'needs-pr-approval', branchName: 'branchName2', }, { ...mock<BranchConfig>(), prTitle: 'pr3', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep3' }], res: 'needs-pr-approval', branchName: 'branchName3', }, { ...mock<BranchConfig>(), prTitle: 'pr4', upgrades: [{ ...mock<PrUpgrade>(), depName: 'dep4' }], res: 'pending', branchName: 'branchName4', }, ]; config.masterIssue = true; config.masterIssuePrApproval = true; await masterIssue.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( config.masterIssueTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( 'lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_approval.txt', 'utf8' ) ); expect(platform.findPr).toHaveBeenCalledTimes(0); // same with dry run await dryRun(branches, platform); }); }); });