1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.bots.pr;
 24 
 25 import org.openjdk.skara.host.*;
 26 import org.openjdk.skara.test.*;
 27 
 28 import org.junit.jupiter.api.*;
 29 
 30 import java.io.IOException;
 31 import java.nio.file.*;
 32 import java.util.*;
 33 import java.util.regex.Pattern;
 34 
 35 import static org.junit.jupiter.api.Assertions.*;
 36 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 37 
 38 class CheckTests {
 39     @Test
 40     void simpleCommit(TestInfo testInfo) throws IOException {
 41         try (var credentials = new HostCredentials(testInfo);
 42              var tempFolder = new TemporaryDirectory()) {
 43             var author = credentials.getHostedRepository();
 44             var reviewer = credentials.getHostedRepository();
 45 
 46             var censusBuilder = credentials.getCensusBuilder()
 47                                            .addAuthor(author.host().getCurrentUserDetails().id())
 48                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
 49             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
 50 
 51             // Populate the projects repository
 52             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
 53             var masterHash = localRepo.resolve("master").orElseThrow();
 54             localRepo.push(masterHash, author.getUrl(), "master", true);
 55 
 56             // Make a change with a corresponding PR
 57             var editHash = CheckableRepository.appendAndCommit(localRepo);
 58             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
 59             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
 60 
 61             // Check the status
 62             TestBotRunner.runPeriodicItems(checkBot);
 63 
 64             // Verify that the check succeeded
 65             var checks = pr.getChecks(editHash);
 66             assertEquals(1, checks.size());
 67             var check = checks.get("jcheck");
 68             assertEquals(CheckStatus.SUCCESS, check.status());
 69 
 70             // The PR should now be ready for review
 71             assertTrue(pr.getLabels().contains("rfr"));
 72             assertFalse(pr.getLabels().contains("ready"));
 73 
 74             // Approve it as another user
 75             var approvalPr = reviewer.getPullRequest(pr.getId());
 76             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
 77 
 78             // Check the status again
 79             TestBotRunner.runPeriodicItems(checkBot);
 80 
 81             // The check should now be successful
 82             checks = pr.getChecks(editHash);
 83             assertEquals(1, checks.size());
 84             check = checks.get("jcheck");
 85             assertEquals(CheckStatus.SUCCESS, check.status());
 86 
 87             // The PR should not be flagged as ready for review, at it is already reviewed
 88             assertFalse(pr.getLabels().contains("rfr"));
 89             assertTrue(pr.getLabels().contains("ready"));
 90         }
 91     }
 92 
 93     @Test
 94     void whitespaceIssue(TestInfo testInfo) throws IOException {
 95         try (var credentials = new HostCredentials(testInfo);
 96              var tempFolder = new TemporaryDirectory()) {
 97 
 98             var author = credentials.getHostedRepository();
 99             var reviewer = credentials.getHostedRepository();
100 
101             var censusBuilder = credentials.getCensusBuilder()
102                                            .addAuthor(author.host().getCurrentUserDetails().id())
103                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
104             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
105 
106             // Populate the projects repository
107             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
108             var masterHash = localRepo.resolve("master").orElseThrow();
109             localRepo.push(masterHash, author.getUrl(), "master", true);
110 
111             // Make a change with a corresponding PR
112             var editHash = CheckableRepository.appendAndCommit(localRepo, "A line with a trailing whitespace   ");
113             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
114             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
115 
116             // Check the status
117             TestBotRunner.runPeriodicItems(checkBot);
118 
119             // The PR should not be flagged as ready for review
120             assertFalse(pr.getLabels().contains("rfr"));
121 
122             // Approve it as another user
123             var approvalPr = reviewer.getPullRequest(pr.getId());
124             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
125 
126             // Check the status
127             TestBotRunner.runPeriodicItems(checkBot);
128 
129             // Verify that the check failed
130             var checks = pr.getChecks(editHash);
131             assertEquals(1, checks.size());
132             var check = checks.get("jcheck");
133             assertEquals(CheckStatus.FAILURE, check.status());
134 
135             // The PR should not still not be flagged as ready for review
136             assertFalse(pr.getLabels().contains("rfr"));
137 
138             // Remove the trailing whitespace in a new commit
139             editHash = CheckableRepository.replaceAndCommit(localRepo, "A line without a trailing whitespace");
140             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
141 
142             // Make sure that the push registered
143             var lastHeadHash = pr.getHeadHash();
144             var refreshCount = 0;
145             do {
146                 pr = author.getPullRequest(pr.getId());
147                 if (refreshCount++ > 100) {
148                     fail("The PR did not update after the new push");
149                 }
150             } while (pr.getHeadHash().equals(lastHeadHash));
151 
152             // Check the status again
153             TestBotRunner.runPeriodicItems(checkBot);
154 
155             // The PR should not be flagged as ready for review, at it is already reviewed
156             assertFalse(pr.getLabels().contains("rfr"));
157 
158             // The check should now be successful
159             checks = pr.getChecks(editHash);
160             assertEquals(1, checks.size());
161             check = checks.get("jcheck");
162             assertEquals(CheckStatus.SUCCESS, check.status());
163         }
164     }
165 
166     @Test
167     void multipleReviews(TestInfo testInfo) throws IOException {
168         try (var credentials = new HostCredentials(testInfo);
169              var tempFolder = new TemporaryDirectory()) {
170 
171             var author = credentials.getHostedRepository();
172             var reviewer = credentials.getHostedRepository();
173             var commenter = credentials.getHostedRepository();
174 
175             var censusBuilder = credentials.getCensusBuilder()
176                                            .addAuthor(author.host().getCurrentUserDetails().id())
177                                            .addReviewer(reviewer.host().getCurrentUserDetails().id())
178                                            .addReviewer(commenter.host().getCurrentUserDetails().id());
179 
180             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
181 
182             // Populate the projects repository
183             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
184             var masterHash = localRepo.resolve("master").orElseThrow();
185             localRepo.push(masterHash, author.getUrl(), "master", true);
186 
187             // Make a change with a corresponding PR
188             var editHash = CheckableRepository.appendAndCommit(localRepo);
189             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
190             var authorPr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
191 
192             // Let the status bot inspect the PR
193             TestBotRunner.runPeriodicItems(checkBot);
194             assertFalse(authorPr.getBody().contains("Approvers"));
195 
196             // Approve it
197             var reviewerPr = reviewer.getPullRequest(authorPr.getId());
198             reviewerPr.addReview(Review.Verdict.APPROVED, "Approved");
199             TestBotRunner.runPeriodicItems(checkBot);
200 
201             // Refresh the PR and check that it has been approved
202             authorPr = author.getPullRequest(authorPr.getId());
203             assertTrue(authorPr.getBody().contains("Approvers"));
204 
205             // Update the file after approval
206             editHash = CheckableRepository.appendAndCommit(localRepo, "Now I've gone and changed it");
207             localRepo.push(editHash, author.getUrl(), "edit", true);
208 
209             // Make sure that the push registered
210             var lastHeadHash = authorPr.getHeadHash();
211             var refreshCount = 0;
212             do {
213                 authorPr = author.getPullRequest(authorPr.getId());
214                 if (refreshCount++ > 100) {
215                     fail("The PR did not update after the new push");
216                 }
217             } while (authorPr.getHeadHash().equals(lastHeadHash));
218 
219             // Check that the review is flagged as stale
220             TestBotRunner.runPeriodicItems(checkBot);
221             authorPr = author.getPullRequest(authorPr.getId());
222             assertTrue(authorPr.getBody().contains("Note"));
223 
224             // Now we can approve it again
225             reviewerPr.addReview(Review.Verdict.APPROVED, "Approved");
226             TestBotRunner.runPeriodicItems(checkBot);
227 
228             // Refresh the PR and check that it has been approved (once) and is no longer stale
229             authorPr = author.getPullRequest(authorPr.getId());
230             assertTrue(authorPr.getBody().contains("Approvers"));
231             assertEquals(1, authorPr.getBody().split("Generated Reviewer", -1).length - 1);
232             assertTrue(authorPr.getReviews().size() >= 1);
233             assertFalse(authorPr.getBody().contains("Note"));
234 
235             // Add a review with disapproval
236             var commenterPr = commenter.getPullRequest(authorPr.getId());
237             commenterPr.addReview(Review.Verdict.DISAPPROVED, "Disapproved");
238             TestBotRunner.runPeriodicItems(checkBot);
239 
240             // Refresh the PR and check that it still only approved once (but two reviews) and is no longer stale
241             authorPr = author.getPullRequest(authorPr.getId());
242             assertTrue(authorPr.getBody().contains("Approvers"));
243             assertEquals(1, authorPr.getBody().split("Generated Reviewer", -1).length - 1);
244             assertTrue(authorPr.getReviews().size() >= 2);
245             assertFalse(authorPr.getBody().contains("Note"));
246         }
247     }
248 
249     @Test
250     void selfReview(TestInfo testInfo) throws IOException {
251         try (var credentials = new HostCredentials(testInfo);
252              var tempFolder = new TemporaryDirectory()) {
253 
254             var author = credentials.getHostedRepository();
255 
256             var censusBuilder = credentials.getCensusBuilder()
257                                            .addReviewer(author.host().getCurrentUserDetails().id());
258 
259             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
260 
261             // Populate the projects repository
262             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
263             var masterHash = localRepo.resolve("master").orElseThrow();
264             localRepo.push(masterHash, author.getUrl(), "master", true);
265 
266             // Make a change with a corresponding PR
267             var editHash = CheckableRepository.appendAndCommit(localRepo);
268             localRepo.push(editHash, author.getUrl(), "edit", true);
269             var authorPr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
270 
271             // Let the status bot inspect the PR
272             TestBotRunner.runPeriodicItems(checkBot);
273             assertFalse(authorPr.getBody().contains("Approvers"));
274 
275             // Approve it
276             authorPr.addReview(Review.Verdict.APPROVED, "Approved");
277             TestBotRunner.runPeriodicItems(checkBot);
278 
279             // Refresh the PR and check that it has been approved
280             authorPr = author.getPullRequest(authorPr.getId());
281             assertTrue(authorPr.getBody().contains("Approvers"));
282 
283             // Verify that the check failed
284             var checks = authorPr.getChecks(editHash);
285             assertEquals(1, checks.size());
286             var check = checks.get("jcheck");
287             assertEquals(CheckStatus.FAILURE, check.status());
288         }
289     }
290 
291     @Test
292     void multipleCommitters(TestInfo testInfo) throws IOException {
293         try (var credentials = new HostCredentials(testInfo);
294              var tempFolder = new TemporaryDirectory()) {
295             var author = credentials.getHostedRepository();
296             var reviewer = credentials.getHostedRepository();
297 
298             var censusBuilder = credentials.getCensusBuilder()
299                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
300             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
301 
302             // Populate the projects repository
303             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
304             var masterHash = localRepo.resolve("master").orElseThrow();
305             localRepo.push(masterHash, author.getUrl(), "master", true);
306 
307             // Make two changes with different authors
308             CheckableRepository.appendAndCommit(localRepo, "First edit", "Edit by number 1",
309                                                 "number1", "number1@none.none");
310             var editHash = CheckableRepository.appendAndCommit(localRepo, "Second edit", "Edit by number 2",
311                                                                "number2", "number2@none.none");
312             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
313             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
314 
315             // Check the status
316             TestBotRunner.runPeriodicItems(checkBot);
317 
318             // Verify that the check failed
319             var checks = pr.getChecks(editHash);
320             assertEquals(1, checks.size());
321             var check = checks.get("jcheck");
322             assertEquals(CheckStatus.FAILURE, check.status());
323 
324             // Approve it as another user
325             var approvalPr = reviewer.getPullRequest(pr.getId());
326             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
327 
328             // Check the status again
329             TestBotRunner.runPeriodicItems(checkBot);
330 
331             // The check should still be failing
332             checks = pr.getChecks(editHash);
333             assertEquals(1, checks.size());
334             check = checks.get("jcheck");
335             assertEquals(CheckStatus.FAILURE, check.status());
336 
337             // The PR should not be flagged as ready for review, as multiple committers is a problem
338             assertFalse(pr.getLabels().contains("rfr"));
339         }
340     }
341 
342     @Test
343     void updatedContentFailsCheck(TestInfo testInfo) throws IOException {
344         try (var credentials = new HostCredentials(testInfo);
345              var tempFolder = new TemporaryDirectory()) {
346             var author = credentials.getHostedRepository();
347             var reviewer = credentials.getHostedRepository();
348 
349             var censusBuilder = credentials.getCensusBuilder()
350                                            .addAuthor(author.host().getCurrentUserDetails().id())
351                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
352             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
353 
354             // Populate the projects repository
355             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
356             var masterHash = localRepo.resolve("master").orElseThrow();
357             localRepo.push(masterHash, author.getUrl(), "master", true);
358 
359             // Make a change with a corresponding PR
360             var editHash = CheckableRepository.appendAndCommit(localRepo);
361             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
362             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
363 
364             // Check the status
365             TestBotRunner.runPeriodicItems(checkBot);
366 
367             // Verify that the check passed
368             var checks = pr.getChecks(editHash);
369             assertEquals(1, checks.size());
370             var check = checks.get("jcheck");
371             assertEquals(CheckStatus.SUCCESS, check.status());
372 
373             // The PR should now be ready for review
374             assertTrue(pr.getLabels().contains("rfr"));
375             assertFalse(pr.getLabels().contains("ready"));
376 
377             // Approve it as another user
378             var approvalPr = reviewer.getPullRequest(pr.getId());
379             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
380 
381             // Check the status again
382             TestBotRunner.runPeriodicItems(checkBot);
383 
384             // The check should now be successful
385             checks = pr.getChecks(editHash);
386             assertEquals(1, checks.size());
387             check = checks.get("jcheck");
388             assertEquals(CheckStatus.SUCCESS, check.status());
389 
390             // The PR should not be flagged as ready for review, at it is already reviewed
391             assertFalse(pr.getLabels().contains("rfr"));
392             assertTrue(pr.getLabels().contains("ready"));
393 
394             var addedHash = CheckableRepository.appendAndCommit(localRepo, "trailing whitespace   ");
395             localRepo.push(addedHash, author.getUrl(), "edit");
396 
397             // Make sure that the push registered
398             var lastHeadHash = pr.getHeadHash();
399             var refreshCount = 0;
400             do {
401                 pr = author.getPullRequest(pr.getId());
402                 if (refreshCount++ > 100) {
403                     fail("The PR did not update after the new push");
404                 }
405             } while (pr.getHeadHash().equals(lastHeadHash));
406 
407             // Check the status
408             TestBotRunner.runPeriodicItems(checkBot);
409 
410             // The PR is now neither ready for review nor integration
411             assertFalse(pr.getLabels().contains("rfr"));
412             assertFalse(pr.getLabels().contains("ready"));
413 
414             // The check should now be failing
415             checks = pr.getChecks(addedHash);
416             assertEquals(1, checks.size());
417             check = checks.get("jcheck");
418             assertEquals(CheckStatus.FAILURE, check.status());
419         }
420     }
421 
422     @Test
423     void individualReviewComments(TestInfo testInfo) throws IOException {
424         try (var credentials = new HostCredentials(testInfo);
425              var tempFolder = new TemporaryDirectory()) {
426             var author = credentials.getHostedRepository();
427             var reviewer = credentials.getHostedRepository();
428 
429             // This test is only relevant on hosts not supporting proper review comment bodies
430             assumeTrue(!author.host().supportsReviewBody());
431 
432             var censusBuilder = credentials.getCensusBuilder()
433                                            .addAuthor(author.host().getCurrentUserDetails().id())
434                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
435             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master");
436 
437             // Populate the projects repository
438             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
439             var masterHash = localRepo.resolve("master").orElseThrow();
440             localRepo.push(masterHash, author.getUrl(), "master", true);
441 
442             // Make a change with a corresponding PR
443             var editHash = CheckableRepository.appendAndCommit(localRepo);
444             localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
445             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
446 
447             // Check the status
448             TestBotRunner.runPeriodicItems(checkBot);
449             var comments = pr.getComments();
450             var commentCount = comments.size();
451 
452             // Approve it as another user
453             var approvalPr = reviewer.getPullRequest(pr.getId());
454             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
455 
456             // Check the status again
457             TestBotRunner.runPeriodicItems(checkBot);
458 
459             // There should now be two additional comments
460             comments = pr.getComments();
461             assertEquals(commentCount + 2, comments.size());
462             var comment = comments.get(commentCount);
463             assertTrue(comment.body().contains(reviewer.host().getCurrentUserDetails().userName()));
464             assertTrue(comment.body().contains("approved"));
465 
466             // Drop the review
467             approvalPr.addReview(Review.Verdict.NONE, "Unreviewed");
468 
469             // Check the status again
470             TestBotRunner.runPeriodicItems(checkBot);
471 
472             // There should now be yet another comment
473             comments = pr.getComments();
474             assertEquals(commentCount + 3, comments.size());
475             comment = comments.get(commentCount + 2);
476             assertTrue(comment.body().contains(reviewer.host().getCurrentUserDetails().userName()));
477             assertTrue(comment.body().contains("comment"));
478 
479             // No changes should not generate additional comments
480             TestBotRunner.runPeriodicItems(checkBot);
481             comments = pr.getComments();
482             assertEquals(commentCount + 3, comments.size());
483         }
484     }
485 
486     @Test
487     void mergeMessage(TestInfo testInfo) throws IOException {
488         try (var credentials = new HostCredentials(testInfo);
489              var tempFolder = new TemporaryDirectory();
490              var pushedFolder = new TemporaryDirectory()) {
491 
492             var author = credentials.getHostedRepository();
493             var integrator = credentials.getHostedRepository();
494             var censusBuilder = credentials.getCensusBuilder()
495                                            .addCommitter(author.host().getCurrentUserDetails().id())
496                                            .addReviewer(integrator.host().getCurrentUserDetails().id());
497             var mergeBot = new PullRequestBot(integrator, censusBuilder.build(), "master");
498 
499             // Populate the projects repository
500             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
501             var masterHash = localRepo.resolve("master").orElseThrow();
502             assertFalse(CheckableRepository.hasBeenEdited(localRepo));
503             localRepo.push(masterHash, author.getUrl(), "master", true);
504 
505             // Make a change with a corresponding PR
506             var editHash = CheckableRepository.appendAndCommit(localRepo);
507             localRepo.push(editHash, author.getUrl(), "edit", true);
508             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
509 
510             // Approve it as another user
511             var approvalPr = integrator.getPullRequest(pr.getId());
512             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
513 
514             // Get all messages up to date
515             TestBotRunner.runPeriodicItems(mergeBot);
516 
517             // Push something unrelated to master
518             localRepo.checkout(masterHash, true);
519             var unrelated = localRepo.root().resolve("unrelated.txt");
520             Files.writeString(unrelated, "Hello");
521             localRepo.add(unrelated);
522             var unrelatedHash = localRepo.commit("Unrelated", "X", "x@y.z");
523             localRepo.push(unrelatedHash, author.getUrl(), "master");
524 
525             // Let the bot see the changes
526             TestBotRunner.runPeriodicItems(mergeBot);
527 
528             // The bot should reply with an ok message
529             var updated = pr.getComments().stream()
530                             .filter(comment -> comment.body().contains("there has been 1 commit"))
531                             .filter(comment -> comment.body().contains("please merge"))
532                             .count();
533             assertEquals(1, updated);
534         }
535     }
536 
537     @Test
538     void cannotRebase(TestInfo testInfo) throws IOException {
539         try (var credentials = new HostCredentials(testInfo);
540              var tempFolder = new TemporaryDirectory();
541              var pushedFolder = new TemporaryDirectory()) {
542 
543             var author = credentials.getHostedRepository();
544             var integrator = credentials.getHostedRepository();
545             var censusBuilder = credentials.getCensusBuilder()
546                                            .addCommitter(author.host().getCurrentUserDetails().id())
547                                            .addReviewer(integrator.host().getCurrentUserDetails().id());
548             var mergeBot = new PullRequestBot(integrator, censusBuilder.build(), "master");
549 
550             // Populate the projects repository
551             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
552             var masterHash = localRepo.resolve("master").orElseThrow();
553             assertFalse(CheckableRepository.hasBeenEdited(localRepo));
554             localRepo.push(masterHash, author.getUrl(), "master", true);
555 
556             // Make a change with a corresponding PR
557             var editHash = CheckableRepository.appendAndCommit(localRepo);
558             localRepo.push(editHash, author.getUrl(), "edit", true);
559             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
560 
561             // Approve it as another user
562             var approvalPr = integrator.getPullRequest(pr.getId());
563             approvalPr.addReview(Review.Verdict.APPROVED, "Approved");
564 
565             // Get all messages up to date
566             TestBotRunner.runPeriodicItems(mergeBot);
567 
568             // Push something conflicting to master
569             localRepo.checkout(masterHash, true);
570             var conflictingHash = CheckableRepository.appendAndCommit(localRepo, "This looks like a conflict");
571             localRepo.push(conflictingHash, author.getUrl(), "master");
572 
573             // Let the bot see the changes
574             TestBotRunner.runPeriodicItems(mergeBot);
575 
576             // The bot should reply with that there is a conflict
577             var updated = pr.getComments().stream()
578                             .filter(comment -> comment.body().contains("there has been 1 commit"))
579                             .filter(comment -> comment.body().contains("cannot be rebased automatically"))
580                             .count();
581             assertEquals(1, updated);
582 
583             // The PR should be flagged as outdated
584             assertTrue(pr.getLabels().contains("outdated"));
585 
586             // Restore the master branch
587             localRepo.push(masterHash, author.getUrl(), "master", true);
588 
589             // Let the bot see the changes
590             TestBotRunner.runPeriodicItems(mergeBot);
591 
592             // The bot should no longer detect a conflict
593             updated = pr.getComments().stream()
594                             .filter(comment -> comment.body().contains("change can now be integrated"))
595                             .count();
596             assertEquals(1, updated);
597 
598             // The PR should not be flagged as outdated
599             assertFalse(pr.getLabels().contains("outdated"));
600         }
601     }
602 
603     @Test
604     void blockingLabel(TestInfo testInfo) throws IOException {
605         try (var credentials = new HostCredentials(testInfo);
606              var tempFolder = new TemporaryDirectory()) {
607             var author = credentials.getHostedRepository();
608             var reviewer = credentials.getHostedRepository();
609 
610             var censusBuilder = credentials.getCensusBuilder()
611                                            .addAuthor(author.host().getCurrentUserDetails().id())
612                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
613             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master", Map.of(), Map.of(),
614                                               Map.of("block", "Test Blocker"), Set.of(), Map.of());
615 
616             // Populate the projects repository
617             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
618             var masterHash = localRepo.resolve("master").orElseThrow();
619             localRepo.push(masterHash, author.getUrl(), "master", true);
620 
621             // Make a change with a corresponding PR
622             var editHash = CheckableRepository.appendAndCommit(localRepo);
623             localRepo.push(editHash, author.getUrl(), "edit", true);
624             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
625             pr.addLabel("block");
626 
627             // Check the status
628             TestBotRunner.runPeriodicItems(checkBot);
629 
630             // Verify that the check failed
631             var checks = pr.getChecks(editHash);
632             assertEquals(1, checks.size());
633             var check = checks.get("jcheck");
634             assertEquals(CheckStatus.FAILURE, check.status());
635             assertTrue(check.summary().orElseThrow().contains("Test Blocker"));
636 
637             // The PR should not yet be ready for review
638             assertTrue(pr.getLabels().contains("block"));
639             assertFalse(pr.getLabels().contains("rfr"));
640             assertFalse(pr.getLabels().contains("ready"));
641 
642             // Check the status again
643             pr.removeLabel("block");
644             TestBotRunner.runPeriodicItems(checkBot);
645 
646             // The PR should now be ready for review
647             assertTrue(pr.getLabels().contains("rfr"));
648             assertFalse(pr.getLabels().contains("ready"));
649         }
650     }
651 
652     @Test
653     void missingReadyLabel(TestInfo testInfo) throws IOException {
654         try (var credentials = new HostCredentials(testInfo);
655              var tempFolder = new TemporaryDirectory()) {
656             var author = credentials.getHostedRepository();
657             var reviewer = credentials.getHostedRepository();
658 
659             var censusBuilder = credentials.getCensusBuilder()
660                                            .addAuthor(author.host().getCurrentUserDetails().id())
661                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
662             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master", Map.of(), Map.of(),
663                                               Map.of(), Set.of("good-to-go"), Map.of());
664 
665             // Populate the projects repository
666             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
667             var masterHash = localRepo.resolve("master").orElseThrow();
668             localRepo.push(masterHash, author.getUrl(), "master", true);
669 
670             // Make a change with a corresponding PR
671             var editHash = CheckableRepository.appendAndCommit(localRepo);
672             localRepo.push(editHash, author.getUrl(), "edit", true);
673             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
674 
675             // Check the status
676             TestBotRunner.runPeriodicItems(checkBot);
677 
678             // Verify that no checks have been run
679             var checks = pr.getChecks(editHash);
680             assertEquals(0, checks.size());
681 
682             // The PR should not yet be ready for review
683             assertFalse(pr.getLabels().contains("rfr"));
684 
685             // Check the status again
686             pr.addLabel("good-to-go");
687             TestBotRunner.runPeriodicItems(checkBot);
688 
689             // The PR should now be ready for review
690             assertTrue(pr.getLabels().contains("rfr"));
691         }
692     }
693 
694     @Test
695     void missingReadyComment(TestInfo testInfo) throws IOException {
696         try (var credentials = new HostCredentials(testInfo);
697              var tempFolder = new TemporaryDirectory()) {
698             var author = credentials.getHostedRepository();
699             var reviewer = credentials.getHostedRepository();
700 
701             var censusBuilder = credentials.getCensusBuilder()
702                                            .addAuthor(author.host().getCurrentUserDetails().id())
703                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
704             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master", Map.of(), Map.of(),
705                                               Map.of(), Set.of(), Map.of(reviewer.host().getCurrentUserDetails().userName(), Pattern.compile("proceed")));
706 
707             // Populate the projects repository
708             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
709             var masterHash = localRepo.resolve("master").orElseThrow();
710             localRepo.push(masterHash, author.getUrl(), "master", true);
711 
712             // Make a change with a corresponding PR
713             var editHash = CheckableRepository.appendAndCommit(localRepo);
714             localRepo.push(editHash, author.getUrl(), "edit", true);
715             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
716 
717             // Check the status
718             TestBotRunner.runPeriodicItems(checkBot);
719 
720             // Verify that no checks have been run
721             var checks = pr.getChecks(editHash);
722             assertEquals(0, checks.size());
723 
724             // The PR should not yet be ready for review
725             assertFalse(pr.getLabels().contains("rfr"));
726 
727             // Check the status again
728             var reviewerPr = reviewer.getPullRequest(pr.getId());
729             reviewerPr.addComment("proceed");
730             TestBotRunner.runPeriodicItems(checkBot);
731 
732             // The PR should now be ready for review
733             assertTrue(pr.getLabels().contains("rfr"));
734         }
735     }
736 
737     @Test
738     void issueIssue(TestInfo testInfo) throws IOException {
739         try (var credentials = new HostCredentials(testInfo);
740              var tempFolder = new TemporaryDirectory()) {
741             var author = credentials.getHostedRepository();
742             var reviewer = credentials.getHostedRepository();
743 
744             var censusBuilder = credentials.getCensusBuilder()
745                                            .addAuthor(author.host().getCurrentUserDetails().id())
746                                            .addReviewer(reviewer.host().getCurrentUserDetails().id());
747             var checkBot = new PullRequestBot(author, censusBuilder.build(), "master", Map.of(), Map.of(),
748                                               Map.of(), Set.of(), Map.of());
749 
750             // Populate the projects repository
751             var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), Path.of("appendable.txt"),
752                                                      Set.of("issues"));
753             var masterHash = localRepo.resolve("master").orElseThrow();
754             localRepo.push(masterHash, author.getUrl(), "master", true);
755 
756             // Make a change with a corresponding PR
757             var editHash = CheckableRepository.appendAndCommit(localRepo);
758             localRepo.push(editHash, author.getUrl(), "edit", true);
759             var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");
760 
761             // Check the status
762             TestBotRunner.runPeriodicItems(checkBot);
763 
764             // Verify that the check failed
765             var checks = pr.getChecks(editHash);
766             assertEquals(1, checks.size());
767             var check = checks.get("jcheck");
768             assertEquals(CheckStatus.FAILURE, check.status());
769 
770             // Add an issue to the title
771             pr.setTitle("1234: This is a pull request");
772 
773             // Check the status again
774             TestBotRunner.runPeriodicItems(checkBot);
775 
776             // The check should now be successful
777             checks = pr.getChecks(editHash);
778             assertEquals(1, checks.size());
779             check = checks.get("jcheck");
780             assertEquals(CheckStatus.SUCCESS, check.status());
781         }
782     }
783 }