diff --git a/services/bots/src/github-webhook/github-webhook.const.ts b/services/bots/src/github-webhook/github-webhook.const.ts index b27218d..92f9ac8 100644 --- a/services/bots/src/github-webhook/github-webhook.const.ts +++ b/services/bots/src/github-webhook/github-webhook.const.ts @@ -15,6 +15,8 @@ export type GetIssueResponse = RestEndpointMethodTypes['issues']['get']['respons export type GetIssueLabelParams = RestEndpointMethodTypes['issues']['getLabel']['parameters']; export type GetIssueLabelResponse = RestEndpointMethodTypes['issues']['getLabel']['response']['data']; +export type PullRequestCreateReviewParams = + RestEndpointMethodTypes['pulls']['createReview']['parameters']; export type Repository = HomeAssistantRepository; diff --git a/services/bots/src/github-webhook/github-webhook.module.ts b/services/bots/src/github-webhook/github-webhook.module.ts index 5a0825f..25eff4b 100644 --- a/services/bots/src/github-webhook/github-webhook.module.ts +++ b/services/bots/src/github-webhook/github-webhook.module.ts @@ -17,6 +17,7 @@ import { IssueCommentCommands } from './handlers/issue_comment_commands/handler' import { IssueLinks } from './handlers/issue_links'; import { LabelBot } from './handlers/label_bot/handler'; import { LabelCleaner } from './handlers/label_cleaner'; +import { MergeConflictChecker } from './handlers/merge_conflict'; import { MonthOfWTH } from './handlers/month_of_wth'; import { NewIntegrationsHandler } from './handlers/new_integrations'; import { PlatinumReview } from './handlers/platinum_review'; @@ -40,6 +41,7 @@ import { ValidateCla } from './handlers/validate-cla'; IssueLinks, LabelBot, LabelCleaner, + MergeConflictChecker, MonthOfWTH, NewIntegrationsHandler, PlatinumReview, diff --git a/services/bots/src/github-webhook/handlers/merge_conflict.ts b/services/bots/src/github-webhook/handlers/merge_conflict.ts new file mode 100644 index 0000000..0becfa2 --- /dev/null +++ b/services/bots/src/github-webhook/handlers/merge_conflict.ts @@ -0,0 +1,27 @@ +import { PullRequestOpenedEvent, PullRequestSynchronizeEvent } from '@octokit/webhooks-types'; +import { EventType, HomeAssistantRepository } from '../github-webhook.const'; +import { WebhookContext } from '../github-webhook.model'; +import { BaseWebhookHandler } from './base'; + +export class MergeConflictChecker extends BaseWebhookHandler { + public allowedEventTypes = [EventType.PULL_REQUEST_OPENED, EventType.PULL_REQUEST_SYNCHRONIZE]; + public allowedRepositories = [HomeAssistantRepository.CORE]; + + async handle(context: WebhookContext) { + // The data in the event is stale, so we need to re-fetch it. + const { data: pullRequest } = await context.github.pulls.get(context.pullRequest()); + + if (pullRequest.mergeable_state !== 'dirty') { + // The Pull request is not dirty. + return; + } + + // Create a review with a comment to let the user know that there is a merge conflict. + await context.github.pulls.createReview( + context.pullRequest({ + body: `There is a merge conflict.`, + event: 'REQUEST_CHANGES', + }), + ); + } +} diff --git a/tests/services/bots/github-webhook/handlers/merge_conflict.spec.ts b/tests/services/bots/github-webhook/handlers/merge_conflict.spec.ts new file mode 100644 index 0000000..d6305c2 --- /dev/null +++ b/tests/services/bots/github-webhook/handlers/merge_conflict.spec.ts @@ -0,0 +1,57 @@ +// @ts-nocheck +import * as assert from 'assert'; +import { WebhookContext } from '../../../../../bots/src/github-webhook/github-webhook.model'; +import { MergeConflictChecker } from '../../../../../services/bots/src/github-webhook/handlers/merge_conflict'; +import { mockWebhookContext } from '../../../../utils/test_context'; +import { loadJsonFixture } from '../../../../utils/fixture'; + +describe('MergeConflictChecker', () => { + let handler: MergeConflictChecker; + let mockContext: WebhookContext; + let getPullResponse: any; + let createReviewContents: PullRequestCreateReviewParams; + + beforeEach(function () { + handler = new MergeConflictChecker(); + getPullResponse = { data: {} }; + createReviewContents = {}; + mockContext = mockWebhookContext({ + eventType: 'pull_request.opened', + payload: loadJsonFixture('pull_request.opened', {}), + github: { + pulls: { + async get() { + return getPullResponse; + }, + async createReview(params: PullRequestCreateReviewParams) { + createReviewContents = params; + }, + }, + }, + }); + }); + + it('PR with clean state', async () => { + mockContext.github.pulls.createReview = jest.fn(); + getPullResponse = { data: { mergeable_state: 'clean' } }; + + await handler.handle(mockContext); + + assert.deepStrictEqual(createReviewContents, {}); + expect(mockContext.github.pulls.createReview).not.toHaveBeenCalled(); + }); + + it('PR with dirty state', async () => { + getPullResponse = { data: { mergeable_state: 'dirty' } }; + + await handler.handle(mockContext); + + assert.deepStrictEqual(createReviewContents, { + body: 'There is a merge conflict.', + event: 'REQUEST_CHANGES', + owner: 'Codertocat', + pull_number: 2, + repo: 'Hello-World', + }); + }); +});