diff --git a/src/Subscriber/CongratulateFirstTimeMergedContributorSubscriber.php b/src/Subscriber/CongratulateFirstTimeMergedContributorSubscriber.php new file mode 100644 index 00000000..f1909d5c --- /dev/null +++ b/src/Subscriber/CongratulateFirstTimeMergedContributorSubscriber.php @@ -0,0 +1,63 @@ + + */ +class CongratulateFirstTimeMergedContributorSubscriber implements EventSubscriberInterface +{ + public function __construct( + private readonly IssueApi $issueApi, + ) { + } + + public function onPullRequest(GitHubEvent $event): void + { + $data = $event->getData(); + if ('closed' !== $data['action'] || !($data['merged'] ?? false)) { + return; + } + + $association = $data['pull_request']['author_association'] ?? ''; + if (!in_array($association, ['NONE', 'FIRST_TIMER', 'FIRST_TIME_CONTRIBUTOR'])) { + return; + } + + $repository = $event->getRepository(); + $pullRequestNumber = $data['pull_request']['number']; + $goodFirstIssueUrl = sprintf( + 'https://github.com/%s/issues?q=is%%3Aissue%%20state%%3Aopen%%20label%%3A%%22Good%%20first%%20issue%%22', + $repository->getFullName() + ); + + $this->issueApi->commentOnIssue($repository, $pullRequestNumber, <<setResponseData([ + 'pull_request' => $pullRequestNumber, + 'first_time_merged_contributor' => true, + ]); + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + GitHubEvents::PULL_REQUEST => 'onPullRequest', + ]; + } +} diff --git a/tests/Subscriber/CongratulateFirstTimeMergedContributorSubscriberTest.php b/tests/Subscriber/CongratulateFirstTimeMergedContributorSubscriberTest.php new file mode 100644 index 00000000..b0132441 --- /dev/null +++ b/tests/Subscriber/CongratulateFirstTimeMergedContributorSubscriberTest.php @@ -0,0 +1,95 @@ +issueApi = $this->createMock(IssueApi::class); + $this->repository = new Repository('symfony', 'symfony', null); + + $this->dispatcher = new EventDispatcher(); + $this->dispatcher->addSubscriber(new CongratulateFirstTimeMergedContributorSubscriber($this->issueApi)); + } + + public function testPostsCommentOnFirstTimeMergedContributor() + { + $this->issueApi->expects($this->once()) + ->method('commentOnIssue') + ->with( + $this->repository, + 1234, + $this->stringContains("You're officially a Symfony contributor — welcome to the club!") + ); + + $this->dispatcher->dispatch(new GitHubEvent([ + 'action' => 'closed', + 'merged' => true, + 'pull_request' => [ + 'number' => 1234, + 'author_association' => 'FIRST_TIME_CONTRIBUTOR', + ], + ], $this->repository), GitHubEvents::PULL_REQUEST); + } + + public function testSkipsNonMergedClosedPR() + { + $this->issueApi->expects($this->never())->method('commentOnIssue'); + + $this->dispatcher->dispatch(new GitHubEvent([ + 'action' => 'closed', + 'merged' => false, + 'pull_request' => [ + 'number' => 1234, + 'author_association' => 'FIRST_TIME_CONTRIBUTOR', + ], + ], $this->repository), GitHubEvents::PULL_REQUEST); + } + + public function testSkipsExistingContributor() + { + $this->issueApi->expects($this->never())->method('commentOnIssue'); + + $this->dispatcher->dispatch(new GitHubEvent([ + 'action' => 'closed', + 'merged' => true, + 'pull_request' => [ + 'number' => 1234, + 'author_association' => 'CONTRIBUTOR', + ], + ], $this->repository), GitHubEvents::PULL_REQUEST); + } + + public function testGoodFirstIssueLinkPointsToCorrectRepo() + { + $this->issueApi->expects($this->once()) + ->method('commentOnIssue') + ->with( + $this->repository, + 1234, + $this->stringContains('https://github.com/symfony/symfony/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Good%20first%20issue%22') + ); + + $this->dispatcher->dispatch(new GitHubEvent([ + 'action' => 'closed', + 'merged' => true, + 'pull_request' => [ + 'number' => 1234, + 'author_association' => 'FIRST_TIMER', + ], + ], $this->repository), GitHubEvents::PULL_REQUEST); + } +}