diff --git a/src/autoupdater.ts b/src/autoupdater.ts index ec45b380..3ecdfcb8 100644 --- a/src/autoupdater.ts +++ b/src/autoupdater.ts @@ -3,6 +3,7 @@ import { GitHub } from '@actions/github/lib/utils'; import * as ghCore from '@actions/core'; import * as octokit from '@octokit/types'; import { ConfigLoader } from './config-loader'; +import { PayloadRepository } from '@actions/github/lib/interfaces'; interface MergeOpts { owner: string; @@ -32,6 +33,56 @@ export class AutoUpdater { ghCore.info(`Handling push event on ref '${ref}'`); + const updated = await this.pulls(ref, repository); + + return updated; + } + + async handlePullRequest(): Promise { + const { action } = this.eventData; + + ghCore.info(`Handling pull_request event triggered by action '${action}'`); + + const isUpdated = await this.update(this.eventData.pull_request); + if (isUpdated) { + ghCore.info( + 'Auto update complete, pull request branch was updated with changes from the base branch.', + ); + } else { + ghCore.info('Auto update complete, no changes were made.'); + } + + return isUpdated; + } + + async handleWorkflowRun(): Promise { + const { workflow_run, repository } = this.eventData; + const branch = workflow_run.head_branch; + const event = workflow_run.event; + + if (workflow_run.event !== 'push') { + ghCore.warning( + `Workflow_run event triggered via ${event} workflow not yet supported.`, + ); + return 0; + } + + // this may not be possible given the check above, but leaving in for now + if (branch.length === 0) { + ghCore.warning('Event was not on a branch, skipping.'); + return 0; + } + + ghCore.info( + `Handling workflow-run event triggered by '${event}' on '${branch}'`, + ); + + const updated = await this.pulls(`refs/heads/${branch}`, repository); + + return updated; + } + + async pulls(ref: string, repository: PayloadRepository): Promise { if (!ref.startsWith('refs/heads/')) { ghCore.warning('Push event was not on a branch, skipping.'); return 0; @@ -41,7 +92,7 @@ export class AutoUpdater { let updated = 0; const paginatorOpts = this.octokit.pulls.list.endpoint.merge({ - owner: repository.owner.name, + owner: repository.owner.name || repository.owner.login, repo: repository.name, base: baseBranch, state: 'open', @@ -70,23 +121,6 @@ export class AutoUpdater { return updated; } - async handlePullRequest(): Promise { - const { action } = this.eventData; - - ghCore.info(`Handling pull_request event triggered by action '${action}'`); - - const isUpdated = await this.update(this.eventData.pull_request); - if (isUpdated) { - ghCore.info( - 'Auto update complete, pull request branch was updated with changes from the base branch.', - ); - } else { - ghCore.info('Auto update complete, no changes were made.'); - } - - return isUpdated; - } - async update(pull: octokit.PullsUpdateResponseData): Promise { const { ref } = pull.head; ghCore.info(`Evaluating pull request #${pull.number}...`); diff --git a/src/router.ts b/src/router.ts index f22fd9c1..561bf373 100644 --- a/src/router.ts +++ b/src/router.ts @@ -24,9 +24,11 @@ export class Router { await this.updater.handlePullRequest(); } else if (eventName === 'push') { await this.updater.handlePush(); + } else if (eventName === 'workflow_run') { + await this.updater.handleWorkflowRun(); } else { throw new Error( - `Unknown event type '${eventName}', only 'push' and 'pull_request' are supported.`, + `Unknown event type '${eventName}', only 'push', 'pull_request' and 'workflow_run' are supported.`, ); } } diff --git a/test/autoupdate.test.ts b/test/autoupdate.test.ts index 4c3d1de9..af5b702e 100644 --- a/test/autoupdate.test.ts +++ b/test/autoupdate.test.ts @@ -23,6 +23,19 @@ const head = 'develop'; const branch = 'not-a-real-branch'; const dummyEvent = { ref: `refs/heads/${branch}`, + repository: { + owner: { + login: owner, + }, + name: repo, + }, +}; +const dummyWorkflowRunPushEvent = { + workflow_run: { + head_branch: branch, + event: 'push', + pull_requests: [], + }, repository: { owner: { name: owner, @@ -409,6 +422,84 @@ describe('test `handlePush`', () => { }); }); +describe('test `handleWorkflowRun`', () => { + const cloneEvent = () => + JSON.parse(JSON.stringify(dummyWorkflowRunPushEvent)); + + test('workflow_run event by push event on a non-branch', async () => { + const event = cloneEvent(); + event.workflow_run.head_branch = ''; + + const updater = new AutoUpdater(config, event); + + const updateSpy = jest.spyOn(updater, 'update').mockResolvedValue(true); + + const updated = await updater.handleWorkflowRun(); + + expect(updated).toEqual(0); + expect(updateSpy).toHaveBeenCalledTimes(0); + }); + + test('workflow_run event by push event on a branch without any PRs', async () => { + const updater = new AutoUpdater(config, dummyWorkflowRunPushEvent); + + const updateSpy = jest.spyOn(updater, 'update').mockResolvedValue(true); + + const scope = nock('https://api.github.com:443') + .get( + `/repos/${owner}/${repo}/pulls?base=${branch}&state=open&sort=updated&direction=desc`, + ) + .reply(200, []); + + const updated = await updater.handleWorkflowRun(); + + expect(updated).toEqual(0); + expect(updateSpy).toHaveBeenCalledTimes(0); + expect(scope.isDone()).toEqual(true); + }); + + test('workflow_run event by push event on a branch with PRs', async () => { + const updater = new AutoUpdater(config, dummyWorkflowRunPushEvent); + + const pullsMock = []; + const expectedPulls = 5; + for (let i = 0; i < expectedPulls; i++) { + pullsMock.push({ + id: i, + number: i, + }); + } + + const updateSpy = jest.spyOn(updater, 'update').mockResolvedValue(true); + + const scope = nock('https://api.github.com:443') + .get( + `/repos/${owner}/${repo}/pulls?base=${branch}&state=open&sort=updated&direction=desc`, + ) + .reply(200, pullsMock); + + const updated = await updater.handleWorkflowRun(); + + expect(updated).toEqual(expectedPulls); + expect(updateSpy).toHaveBeenCalledTimes(expectedPulls); + expect(scope.isDone()).toEqual(true); + }); + + test('workflow_run event by pull_request event not supported', async () => { + const event = cloneEvent(); + event.workflow_run.event = 'pull_request'; + + const updater = new AutoUpdater(config, event); + + const updateSpy = jest.spyOn(updater, 'update').mockResolvedValue(true); + + const updated = await updater.handleWorkflowRun(); + + expect(updated).toEqual(0); + expect(updateSpy).toHaveBeenCalledTimes(0); + }); +}); + describe('test `handlePullRequest`', () => { test('pull request event with an update triggered', async () => { const updater = new AutoUpdater(config, { diff --git a/test/router.test.ts b/test/router.test.ts index 942745fb..b1a38700 100644 --- a/test/router.test.ts +++ b/test/router.test.ts @@ -15,7 +15,7 @@ test('invalid event name', async () => { const eventName = 'not-a-real-event'; await expect(router.route(eventName)).rejects.toThrowError( - `Unknown event type '${eventName}', only 'push' and 'pull_request' are supported.`, + `Unknown event type '${eventName}', only 'push', 'pull_request' and 'workflow_run' are supported.`, ); const autoUpdateInstance = (AutoUpdater as jest.Mock).mock.instances[0]; @@ -42,3 +42,13 @@ test('"pull_request" events', async () => { const autoUpdateInstance = (AutoUpdater as jest.Mock).mock.instances[0]; expect(autoUpdateInstance.handlePullRequest).toHaveBeenCalledTimes(1); }); + +test('"workflow_run" events', async () => { + const router = new Router(config, {}); + expect(AutoUpdater).toHaveBeenCalledTimes(1); + + await router.route('workflow_run'); + + const autoUpdateInstance = (AutoUpdater as jest.Mock).mock.instances[0]; + expect(autoUpdateInstance.handleWorkflowRun).toHaveBeenCalledTimes(1); +});