From 1f42c35ebc0ab6cc104408b9e7d8c225d2b75f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 2 May 2024 11:27:56 +0200 Subject: [PATCH] Fix GitHubService when adding a new history issue and one already exists whose title is the prefix of the new one --- .../github/lottery/LotteryService.java | 2 +- .../lottery/github/GitHubDedicatedIssue.java | 4 - .../lottery/github/GitHubRepository.java | 211 +++++++++--------- .../github/lottery/github/TopicRef.java | 30 +++ .../lottery/history/HistoryService.java | 11 +- .../github/lottery/notification/Notifier.java | 15 +- .../quarkus/github/lottery/util/Streams.java | 5 + .../github/lottery/GitHubServiceTest.java | 167 ++++++++++---- .../github/lottery/HistoryServiceTest.java | 25 ++- .../lottery/LotterySingleRepositoryTest.java | 6 +- .../lottery/NotificationServiceTest.java | 30 ++- 11 files changed, 327 insertions(+), 179 deletions(-) delete mode 100644 src/main/java/io/quarkus/github/lottery/github/GitHubDedicatedIssue.java create mode 100644 src/main/java/io/quarkus/github/lottery/github/TopicRef.java diff --git a/src/main/java/io/quarkus/github/lottery/LotteryService.java b/src/main/java/io/quarkus/github/lottery/LotteryService.java index 1396a44..8ad8b00 100644 --- a/src/main/java/io/quarkus/github/lottery/LotteryService.java +++ b/src/main/java/io/quarkus/github/lottery/LotteryService.java @@ -116,7 +116,7 @@ private List registerParticipants(DrawRef drawRef, Lottery lottery, continue; } - if (notifier.hasClosedDedicatedIssue(username)) { + if (notifier.isIgnoring(username)) { Log.debugf("Skipping user %s whose dedicated issue is closed", username); continue; } diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubDedicatedIssue.java b/src/main/java/io/quarkus/github/lottery/github/GitHubDedicatedIssue.java deleted file mode 100644 index bfb8cb6..0000000 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubDedicatedIssue.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.quarkus.github.lottery.github; - -public class GitHubDedicatedIssue { -} diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java b/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java index 5d8f3b9..186c956 100644 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java @@ -12,13 +12,10 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; -import io.quarkus.github.lottery.message.MessageFormatter; -import io.quarkus.github.lottery.util.Streams; import org.kohsuke.github.GHDirection; import org.kohsuke.github.GHIssue; import org.kohsuke.github.GHIssueComment; @@ -33,6 +30,8 @@ import io.quarkiverse.githubapp.GitHubClientProvider; import io.quarkiverse.githubapp.GitHubConfigFileProvider; import io.quarkus.github.lottery.config.LotteryConfig; +import io.quarkus.github.lottery.message.MessageFormatter; +import io.quarkus.github.lottery.util.Streams; import io.quarkus.logging.Log; import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; @@ -200,7 +199,7 @@ private IssueActionSide lastActionSide(GHIssue ghIssue, Set initialActio queryCommentsBuilder.since(Date.from(lastEventActionSideInstant)); } - Optional lastComment = toStream(queryCommentsBuilder.list()).reduce(last()); + Optional lastComment = toStream(queryCommentsBuilder.list()).reduce(Streams.last()); if (lastComment.isEmpty()) { // No action since the label was assigned. return IssueActionSide.TEAM; @@ -216,117 +215,124 @@ private Function toIssueRecord() { } /** - * Checks whether an issue identified by its assignee and topic (title prefix) exists, but has been closed. + * Retrieves a topic backed by GitHub issues. * - * @param assignee The GitHub username of issue assignee. - * @param topic The issue's topic, a string that should be prefixed to the issue title. - * - * @throws IOException If a GitHub API call fails. - * @throws java.io.UncheckedIOException If a GitHub API call fails. - * @see #commentOnDedicatedIssue(String, String, String, String) + * @param ref The topic reference. */ - public boolean hasClosedDedicatedIssue(String assignee, String topic) - throws IOException { - var existingIssue = getDedicatedIssue(assignee, topic); - return existingIssue.isPresent() && GHIssueState.CLOSED.equals(existingIssue.get().getState()); + public Topic topic(TopicRef ref) { + return new Topic(ref); } - /** - * Adds a comment to an issue identified by its assignee and topic (title prefix). - * - * @param assignee The GitHub username of issue assignee. - * Two calls with the same username + topic will result in a comment on the same issue. - * @param topic The issue's topic, a string that will be prefixed to the issue title. - * Two calls with the same username + topic will result in a comment on the same issue. - * @param topicSuffix A string that should be appended to the topic in the issue title. - * Each time that suffix changes for a new comment, - * the issue title will be updated, - * and so will the subject of any email notification sent as a result of that comment. - * In conversation-based email clients such as GMail, - * this will result in the comment appearing in a new conversation, - * which can be useful to avoid huge conversations - * @param markdownBody The body of the comment to add. - * - * @throws IOException If a GitHub API call fails. - * @throws java.io.UncheckedIOException If a GitHub API call fails. - */ - public void commentOnDedicatedIssue(String assignee, String topic, String topicSuffix, String markdownBody) - throws IOException { - String targetTitle = topic + topicSuffix; - var existingIssue = getDedicatedIssue(assignee, topic); - GHIssue issue; - if (existingIssue.isPresent()) { - issue = existingIssue.get(); - if (!issue.getTitle().equals(targetTitle)) { - issue.setTitle(targetTitle); - } - if (GHIssueState.CLOSED.equals(issue.getState())) { - issue.reopen(); - } - try { - // We try to minimize the last comment on a best-effort basis, - // taking into account only recent comments, - // to avoid performance hogs on issues with many comments. - // (There's no way to retrieve comments of an issue in anti-chronological order...) - Optional lastRecentComment = getAppCommentsSince(issue, - clock.instant().minus(21, ChronoUnit.DAYS)) - .reduce(last()); - lastRecentComment.ifPresent(this::minimizeOutdatedComment); - } catch (Exception e) { - Log.errorf(e, "Failed to minimize last notification for issue %s#%s", ref.repositoryName(), issue.getNumber()); - } + public class Topic { + private final TopicRef ref; - // Update the issue description with the content of the latest comment, - // for convenience. - // This must be done before the comment, so that notifications triggered by the comment are only sent - // when the issue is fully updated. - issue.setBody(messageFormatter.formatDedicatedIssueBodyMarkdown(topic, markdownBody)); - } else { - issue = createDedicatedIssue(assignee, targetTitle, topic, markdownBody); + private Topic(TopicRef ref) { + this.ref = ref; } - issue.comment(markdownBody); - } + /** + * Checks whether all issues of this topic exist, but have been closed. + * + * @throws IOException If a GitHub API call fails. + * @throws java.io.UncheckedIOException If a GitHub API call fails. + * @see #comment(String, String) + */ + public boolean isClosed() throws IOException { + var existingIssue = getDedicatedIssues().findFirst(); + return existingIssue.isPresent() && GHIssueState.CLOSED.equals(existingIssue.get().getState()); + } - public Stream extractCommentsFromDedicatedIssue(String assignee, String topic, Instant since) - throws IOException { - return getDedicatedIssue(assignee, topic) - .map(uncheckedIO(issue -> getAppCommentsSince(issue, since))) - .orElse(Stream.of()) - .map(GHIssueComment::getBody); - } + /** + * Adds a comment to an issue identified by its assignee and topic (title prefix). + * + * @param topicSuffix A string that should be appended to the topic in the issue title. + * Each time that suffix changes for a new comment, + * the issue title will be updated, + * and so will the subject of any email notification sent as a result of that comment. + * In conversation-based email clients such as GMail, + * this will result in the comment appearing in a new conversation, + * which can be useful to avoid huge conversations. + * @param markdownBody The body of the comment to add. + * + * @throws IOException If a GitHub API call fails. + * @throws java.io.UncheckedIOException If a GitHub API call fails. + */ + public void comment(String topicSuffix, String markdownBody) + throws IOException { + var dedicatedIssue = getDedicatedIssues().findFirst(); + if (ref.expectedSuffixStart() != null && !topicSuffix.startsWith(ref.expectedSuffixStart()) + || ref.expectedSuffixStart() == null && !topicSuffix.isEmpty()) { + throw new IllegalArgumentException( + "expectedSuffixStart = '%s' but topicSuffix = '%s'".formatted(ref.expectedSuffixStart(), topicSuffix)); + } + String targetTitle = ref.topic() + topicSuffix; + GHIssue issue; + if (dedicatedIssue.isPresent()) { + issue = dedicatedIssue.get(); + if (!issue.getTitle().equals(targetTitle)) { + issue.setTitle(targetTitle); + } + if (GHIssueState.CLOSED.equals(issue.getState())) { + issue.reopen(); + } + try { + // We try to minimize the last comment on a best-effort basis, + // taking into account only recent comments, + // to avoid performance hogs on issues with many comments. + // (There's no way to retrieve comments of an issue in anti-chronological order...) + Optional lastRecentComment = getAppCommentsSince(issue, + clock.instant().minus(21, ChronoUnit.DAYS)) + .reduce(Streams.last()); + lastRecentComment.ifPresent(GitHubRepository.this::minimizeOutdatedComment); + } catch (Exception e) { + Log.errorf(e, "Failed to minimize last notification for issue %s#%s", + GitHubRepository.this.ref.repositoryName(), issue.getNumber()); + } + + // Update the issue description with the content of the latest comment, + // for convenience. + // This must be done before the comment, so that notifications triggered by the comment are only sent + // when the issue is fully updated. + issue.setBody(messageFormatter.formatDedicatedIssueBodyMarkdown(ref.topic(), markdownBody)); + } else { + issue = createDedicatedIssue(targetTitle, markdownBody); + } - private Optional getDedicatedIssue(String assignee, String topic) throws IOException { - var builder = repository().queryIssues().creator(appLogin()); - if (assignee != null) { - builder.assignee(assignee); + issue.comment(markdownBody); } - builder.state(GHIssueState.ALL); - // Try exact match first to avoid confusion if there are two issues and one is - // the exact topic while the other just starts with the topic. - // Example: - // topic = Lottery history for quarkusio/quarkus - // issue1.title = Lottery history for quarkusio/quarkusio.github.io - // issue2.title = Lottery history for quarkusio/quarkus - for (var issue : builder.list()) { - if (issue.getTitle().equals(topic)) { - return Optional.of(issue); + + private Stream getDedicatedIssues() throws IOException { + var builder = repository().queryIssues().creator(appLogin()); + if (ref.assignee() != null) { + builder.assignee(ref.assignee()); } + builder.state(GHIssueState.ALL); + return Streams.toStream(builder.list()) + .filter(ref.expectedSuffixStart() != null + ? issue -> issue.getTitle().startsWith(ref.topic() + ref.expectedSuffixStart()) + // Try exact match in this case to avoid confusion if there are two issues and one is + // the exact topic while the other just starts with the topic. + // Example: + // topic = Lottery history for quarkusio/quarkus + // issue1.title = Lottery history for quarkusio/quarkusio.github.io + // issue2.title = Lottery history for quarkusio/quarkus + : issue -> issue.getTitle().equals(ref.topic())); } - for (var issue : builder.list()) { - if (issue.getTitle().startsWith(topic)) { - return Optional.of(issue); - } + + public Stream extractComments(Instant since) + throws IOException { + return getDedicatedIssues() + .flatMap(uncheckedIO(issue -> getAppCommentsSince(issue, since))) + .map(GHIssueComment::getBody); } - return Optional.empty(); - } - private GHIssue createDedicatedIssue(String username, String title, String topic, String lastCommentMarkdownBody) - throws IOException { - return repository().createIssue(title) - .assignee(username) - .body(messageFormatter.formatDedicatedIssueBodyMarkdown(topic, lastCommentMarkdownBody)) - .create(); + private GHIssue createDedicatedIssue(String title, String lastCommentMarkdownBody) + throws IOException { + return repository().createIssue(title) + .assignee(ref.assignee()) + .body(messageFormatter.formatDedicatedIssueBodyMarkdown(ref.topic(), lastCommentMarkdownBody)) + .create(); + } } private Stream getAppCommentsSince(GHIssue issue, Instant since) { @@ -355,7 +361,4 @@ mutation MinimizeOutdatedContent($subjectId: ID!) { } } - private BinaryOperator last() { - return (first, second) -> second; - } } diff --git a/src/main/java/io/quarkus/github/lottery/github/TopicRef.java b/src/main/java/io/quarkus/github/lottery/github/TopicRef.java new file mode 100644 index 0000000..7e81d20 --- /dev/null +++ b/src/main/java/io/quarkus/github/lottery/github/TopicRef.java @@ -0,0 +1,30 @@ +package io.quarkus.github.lottery.github; + +/** + * A reference to a {@link GitHubRepository#topic(TopicRef) topic} in a GitHub repository. + * + * @param assignee The GitHub username of issue assignee. + * @param topic The issue's topic, a string that should be prefixed to the title of dedicated issues. + * @param expectedSuffixStart If issue titles are suffixed (e.g. with the last update date), + * the (constant) string these suffixes are expected to start with. + * If issue titles are identical to the topic, a {@code null} string. + */ +public record TopicRef(String assignee, + String topic, + String expectedSuffixStart) { + + public TopicRef { + if (expectedSuffixStart != null && expectedSuffixStart.isEmpty()) { + throw new IllegalArgumentException("expectedSuffixStart must not be empty; pass either null or a non-empty string"); + } + } + + public static TopicRef history(String topic) { + return new TopicRef(null, topic, null); + } + + public static TopicRef notification(String assignee, String topic) { + return new TopicRef(assignee, topic, " (updated"); + } + +} diff --git a/src/main/java/io/quarkus/github/lottery/history/HistoryService.java b/src/main/java/io/quarkus/github/lottery/history/HistoryService.java index 06cdc89..f0525d9 100644 --- a/src/main/java/io/quarkus/github/lottery/history/HistoryService.java +++ b/src/main/java/io/quarkus/github/lottery/history/HistoryService.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.List; +import io.quarkus.github.lottery.github.TopicRef; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -27,8 +28,7 @@ public class HistoryService { public LotteryHistory fetch(DrawRef drawRef, LotteryConfig config) throws IOException { var persistenceRepo = persistenceRepo(drawRef, config); var history = new LotteryHistory(drawRef.instant(), config.buckets()); - String historyTopic = messageFormatter.formatHistoryTopicText(drawRef); - persistenceRepo.extractCommentsFromDedicatedIssue(null, historyTopic, history.since()) + persistenceRepo.topic(historyTopic(drawRef)).extractComments(history.since()) .flatMap(uncheckedIO(message -> messageFormatter.extractPayloadFromHistoryBodyMarkdown(message).stream())) .forEach(history::add); return history; @@ -36,9 +36,12 @@ public LotteryHistory fetch(DrawRef drawRef, LotteryConfig config) throws IOExce public void append(DrawRef drawRef, LotteryConfig config, List reports) throws IOException { var persistenceRepo = persistenceRepo(drawRef, config); - String historyTopic = messageFormatter.formatHistoryTopicText(drawRef); String commentBody = messageFormatter.formatHistoryBodyMarkdown(drawRef, reports); - persistenceRepo.commentOnDedicatedIssue(null, historyTopic, "", commentBody); + persistenceRepo.topic(historyTopic(drawRef)).comment("", commentBody); + } + + private TopicRef historyTopic(DrawRef drawRef) { + return TopicRef.history(messageFormatter.formatHistoryTopicText(drawRef)); } GitHubRepository persistenceRepo(DrawRef drawRef, LotteryConfig config) { diff --git a/src/main/java/io/quarkus/github/lottery/notification/Notifier.java b/src/main/java/io/quarkus/github/lottery/notification/Notifier.java index 84e84b9..50cd505 100644 --- a/src/main/java/io/quarkus/github/lottery/notification/Notifier.java +++ b/src/main/java/io/quarkus/github/lottery/notification/Notifier.java @@ -5,6 +5,7 @@ import io.quarkus.github.lottery.draw.DrawRef; import io.quarkus.github.lottery.draw.LotteryReport; import io.quarkus.github.lottery.github.GitHubRepository; +import io.quarkus.github.lottery.github.TopicRef; import io.quarkus.github.lottery.message.MessageFormatter; public class Notifier implements AutoCloseable { @@ -24,9 +25,9 @@ public void close() { notificationRepository.close(); } - public boolean hasClosedDedicatedIssue(String username) throws IOException { - String topic = formatter.formatNotificationTopicText(drawRef, username); - return notificationRepository.hasClosedDedicatedIssue(username, topic); + public boolean isIgnoring(String username) throws IOException { + return notificationRepository.topic(notificationTopic(username)) + .isClosed(); } public void send(LotteryReport report) throws IOException { @@ -34,9 +35,13 @@ public void send(LotteryReport report) throws IOException { throw new IllegalStateException("Cannot send reports for different draws; expected '" + drawRef + "', got '" + report.drawRef() + "'."); } - String topic = formatter.formatNotificationTopicText(drawRef, report.username()); String topicSuffix = formatter.formatNotificationTopicSuffixText(report); String body = formatter.formatNotificationBodyMarkdown(report, notificationRepository.ref()); - notificationRepository.commentOnDedicatedIssue(report.username(), topic, topicSuffix, body); + notificationRepository.topic(notificationTopic(report.username())) + .comment(topicSuffix, body); + } + + private TopicRef notificationTopic(String username) { + return TopicRef.notification(username, formatter.formatNotificationTopicText(drawRef, username)); } } diff --git a/src/main/java/io/quarkus/github/lottery/util/Streams.java b/src/main/java/io/quarkus/github/lottery/util/Streams.java index c0207a0..e75b46b 100644 --- a/src/main/java/io/quarkus/github/lottery/util/Streams.java +++ b/src/main/java/io/quarkus/github/lottery/util/Streams.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Spliterators; +import java.util.function.BinaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -64,4 +65,8 @@ public T next() { public static Stream toStream(PagedIterable iterable) { return StreamSupport.stream(iterable.spliterator(), false); } + + public static BinaryOperator last() { + return (first, second) -> second; + } } diff --git a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java index 9ead34b..93a4e0e 100644 --- a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java @@ -39,10 +39,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import io.quarkus.github.lottery.github.GitHubInstallationRef; -import io.quarkus.github.lottery.github.IssueActionSide; -import io.quarkus.github.lottery.message.MessageFormatter; -import io.quarkus.test.junit.QuarkusMock; import org.kohsuke.github.GHApp; import org.kohsuke.github.GHAppInstallation; import org.kohsuke.github.GHAuthenticatedAppInstallation; @@ -64,8 +60,13 @@ import io.quarkiverse.githubapp.testing.GitHubAppTest; import io.quarkus.github.lottery.config.LotteryConfig; +import io.quarkus.github.lottery.github.GitHubInstallationRef; import io.quarkus.github.lottery.github.GitHubRepositoryRef; import io.quarkus.github.lottery.github.GitHubService; +import io.quarkus.github.lottery.github.IssueActionSide; +import io.quarkus.github.lottery.github.TopicRef; +import io.quarkus.github.lottery.message.MessageFormatter; +import io.quarkus.test.junit.QuarkusMock; import io.quarkus.test.junit.QuarkusTest; /** @@ -640,7 +641,7 @@ void issuesLastActedOnByAndLastUpdatedBefore_outsider() throws IOException { } @Test - void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Exception { + void topic_extractComments_dedicatedIssueDoesNotExist() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); @@ -660,8 +661,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Excep .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue(null, - "Lottery history for quarkusio/quarkus", since)) + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) .isEmpty(); }) .then().github(mocks -> { @@ -673,7 +674,40 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Excep } @Test - void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExist() throws Exception { + void topic_extractComments_dedicatedIssueDoesNotExist_withConfusingOther() throws Exception { + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); + var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); + + var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + withSettings().defaultAnswer(Answers.RETURNS_SELF)); + + given() + .github(mocks -> { + var repositoryMock = mocks.repository(repoRef.repositoryName()); + + when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + var issue1Mock = mockIssueForNotification(mocks, 1, "Lottery history for quarkusio/quarkusio.github.io"); + var issue2Mock = mockIssueForNotification(mocks, 2, "Another unrelated issue"); + var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); + when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + }) + .when(() -> { + var repo = gitHubService.repository(repoRef); + + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) + .isEmpty(); + }) + .then().github(mocks -> { + verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); + verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(mocks.ghObjects()); + }); + } + + @Test + void topic_extractComments_dedicatedIssueExists_appCommentsDoNotExist() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); @@ -706,8 +740,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExis .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue(null, - "Lottery history for quarkusio/quarkus", since)) + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) .isEmpty(); }) .then().github(mocks -> { @@ -721,7 +755,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExis } @Test - void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_allTooOld() throws Exception { + void topic_extractComments_dedicatedIssueExists_appCommentsExist_allTooOld() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); @@ -747,8 +781,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_all .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue(null, - "Lottery history for quarkusio/quarkus", since)) + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) .isEmpty(); }) .then().github(mocks -> { @@ -762,7 +796,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_all } @Test - void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() throws Exception { + void topic_extractComments_dedicatedIssueExists_appCommentsExist() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); @@ -801,8 +835,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() t .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue(null, - "Lottery history for quarkusio/quarkus", since)) + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) .containsExactly("issue2Comment1Mock#body", "issue2Comment2Mock#body"); }) .then().github(mocks -> { @@ -816,7 +850,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() t } @Test - void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_withConfusingOther() throws Exception { + void topic_extractComments_dedicatedIssueExists_appCommentsExist_withConfusingOther() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var since = LocalDateTime.of(2017, 11, 6, 19, 0).toInstant(ZoneOffset.UTC); @@ -855,8 +889,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_wit .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue(null, - "Lottery history for quarkusio/quarkus", since)) + assertThat(repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .extractComments(since)) .containsExactly("issue2Comment1Mock#body", "issue2Comment2Mock#body"); }) .then().github(mocks -> { @@ -871,7 +905,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_wit @SuppressWarnings("unchecked") @Test - void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { + void topic_comment_dedicatedIssueExists_open() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; @@ -921,8 +955,8 @@ void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { .when(() -> { var repo = gitHubService.repository(repoRef); - repo.commentOnDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", "Some content"); + repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); }) .then().github(mocks -> { verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); @@ -947,7 +981,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { @SuppressWarnings("unchecked") @Test - void commentOnDedicatedIssue_dedicatedIssueExists_noTopicSuffix() throws Exception { + void topic_comment_dedicatedIssueExists_noTopicSuffix() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; @@ -996,12 +1030,11 @@ void commentOnDedicatedIssue_dedicatedIssueExists_noTopicSuffix() throws Excepti .when(() -> { var repo = gitHubService.repository(repoRef); - repo.commentOnDedicatedIssue("quarkus-github-lottery[bot]", - "Lottery history for quarkusio/quarkus", "", "Some content"); + repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .comment("", "Some content"); }) .then().github(mocks -> { verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("quarkus-github-lottery[bot]"); verify(queryIssuesBuilderMock).state(GHIssueState.ALL); verify(queryCommentsBuilderMock).since(Date.from(now.minus(21, ChronoUnit.DAYS))); @@ -1021,7 +1054,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_noTopicSuffix() throws Excepti @SuppressWarnings("unchecked") @Test - void commentOnDedicatedIssue_dedicatedIssueExists_withConfusingOther() throws Exception { + void topic_comment_dedicatedIssueExists_withConfusingOther() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; @@ -1070,12 +1103,11 @@ void commentOnDedicatedIssue_dedicatedIssueExists_withConfusingOther() throws Ex .when(() -> { var repo = gitHubService.repository(repoRef); - repo.commentOnDedicatedIssue("quarkus-github-lottery[bot]", - "Lottery history for quarkusio/quarkus", "", "Some content"); + repo.topic(TopicRef.history("Lottery history for quarkusio/quarkus")) + .comment("", "Some content"); }) .then().github(mocks -> { verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("quarkus-github-lottery[bot]"); verify(queryIssuesBuilderMock).state(GHIssueState.ALL); verify(queryCommentsBuilderMock).since(Date.from(now.minus(21, ChronoUnit.DAYS))); @@ -1095,7 +1127,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_withConfusingOther() throws Ex @SuppressWarnings("unchecked") @Test - void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { + void topic_comment_dedicatedIssueExists_closed() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; @@ -1145,8 +1177,8 @@ void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { .when(() -> { var repo = gitHubService.repository(repoRef); - repo.commentOnDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", "Some content"); + repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); }) .then().github(mocks -> { verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); @@ -1172,7 +1204,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { } @Test - void commentOnDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { + void topic_comment_dedicatedIssueDoesNotExist() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); @@ -1199,8 +1231,56 @@ void commentOnDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { .when(() -> { var repo = gitHubService.repository(repoRef); - repo.commentOnDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", "Some content"); + repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); + }) + .then().github(mocks -> { + var repositoryMock = mocks.repository(repoRef.repositoryName()); + + verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); + verify(queryIssuesBuilderMock).assignee("yrodiere"); + verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(repositoryMock) + .createIssue("yrodiere's report for quarkusio/quarkus (updated 2017-11-06T06:00:00Z)"); + verify(issueBuilderMock).assignee("yrodiere"); + verify(issueBuilderMock).body("Dedicated issue body"); + verify(mocks.issue(2)).comment("Some content"); + + verifyNoMoreInteractions(messageFormatterMock, queryIssuesBuilderMock, issueBuilderMock); + verifyNoMoreInteractions(mocks.ghObjects()); + }); + } + + @Test + void topic_comment_dedicatedIssueDoesNotExist_withConfusingOther() throws IOException { + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); + var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + withSettings().defaultAnswer(Answers.RETURNS_SELF)); + var issueBuilderMock = Mockito.mock(GHIssueBuilder.class, + withSettings().defaultAnswer(Answers.RETURNS_SELF)); + + given() + .github(mocks -> { + var repositoryMock = mocks.repository(repoRef.repositoryName()); + + when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + var issue1Mock = mockIssueForNotification(mocks, 1, "yrodiere's report for quarkusio/quarkusio.githbub.io"); + var issuesMocks = mockPagedIterable(issue1Mock); + when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + + when(repositoryMock.createIssue(any())).thenReturn(issueBuilderMock); + var issue2Mock = mocks.issue(2); + when(issueBuilderMock.create()).thenReturn(issue2Mock); + + when(messageFormatterMock.formatDedicatedIssueBodyMarkdown("yrodiere's report for quarkusio/quarkus", + "Some content")) + .thenReturn("Dedicated issue body"); + }) + .when(() -> { + var repo = gitHubService.repository(repoRef); + + repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); }) .then().github(mocks -> { var repositoryMock = mocks.repository(repoRef.repositoryName()); @@ -1220,7 +1300,7 @@ void commentOnDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { } @Test - void hasClosedDedicatedIssue_dedicatedIssueExists_open() throws Exception { + void topic_isClosed_dedicatedIssueExists_open() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); @@ -1246,7 +1326,8 @@ void hasClosedDedicatedIssue_dedicatedIssueExists_open() throws Exception { .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.hasClosedDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus")) + assertThat(repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .isClosed()) .isFalse(); }) .then().github(mocks -> { @@ -1260,7 +1341,7 @@ void hasClosedDedicatedIssue_dedicatedIssueExists_open() throws Exception { } @Test - void hasClosedDedicatedIssue_dedicatedIssueExists_closed() throws Exception { + void topic_isClosed_dedicatedIssueExists_closed() throws Exception { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); @@ -1286,7 +1367,8 @@ void hasClosedDedicatedIssue_dedicatedIssueExists_closed() throws Exception { .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.hasClosedDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus")) + assertThat(repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .isClosed()) .isTrue(); }) .then().github(mocks -> { @@ -1300,7 +1382,7 @@ void hasClosedDedicatedIssue_dedicatedIssueExists_closed() throws Exception { } @Test - void hasClosedDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { + void topic_isClosed_dedicatedIssueDoesNotExist() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); @@ -1317,7 +1399,8 @@ void hasClosedDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.hasClosedDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus")) + assertThat(repo.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus")) + .isClosed()) .isFalse(); }) .then().github(mocks -> { diff --git a/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java b/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java index 6cf552e..1c258ea 100644 --- a/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java @@ -23,16 +23,17 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import io.quarkus.github.lottery.github.GitHubInstallationRef; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import io.quarkus.github.lottery.config.LotteryConfig; import io.quarkus.github.lottery.draw.DrawRef; import io.quarkus.github.lottery.draw.LotteryReport; +import io.quarkus.github.lottery.github.GitHubInstallationRef; import io.quarkus.github.lottery.github.GitHubRepository; import io.quarkus.github.lottery.github.GitHubRepositoryRef; import io.quarkus.github.lottery.github.GitHubService; +import io.quarkus.github.lottery.github.TopicRef; import io.quarkus.github.lottery.history.HistoryService; import io.quarkus.github.lottery.message.MessageFormatter; import io.quarkus.test.junit.QuarkusMock; @@ -66,6 +67,7 @@ private static LotteryConfig defaultConfig() { GitHubService gitHubServiceMock; GitHubRepository persistenceRepoMock; + GitHubRepository.Topic topicMock; MessageFormatter messageFormatterMock; @@ -85,6 +87,7 @@ void setup() { repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); persistenceRepoMock = Mockito.mock(GitHubRepository.class); + topicMock = Mockito.mock(GitHubRepository.Topic.class); now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); drawRef = new DrawRef(repoRef, now); @@ -103,7 +106,9 @@ void lastNotificationToday_noHistory() throws Exception { String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); - when(persistenceRepoMock.extractCommentsFromDedicatedIssue(isNull(), eq(topic), any())) + when(persistenceRepoMock.topic(TopicRef.history(topic))) + .thenReturn(topicMock); + when(topicMock.extractComments(any())) .thenAnswer(ignored -> Stream.of()); var history = historyService.fetch(drawRef, config); @@ -129,7 +134,9 @@ void lastNotificationToday_notNotified() throws Exception { String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String historyBody = "Some content"; - when(persistenceRepoMock.extractCommentsFromDedicatedIssue(isNull(), eq(topic), any())) + when(persistenceRepoMock.topic(TopicRef.history(topic))) + .thenReturn(topicMock); + when(topicMock.extractComments(any())) .thenAnswer(ignored -> Stream.of(historyBody)); when(messageFormatterMock.extractPayloadFromHistoryBodyMarkdown(historyBody)) .thenReturn(List.of( @@ -165,7 +172,9 @@ void lastNotificationToday_notifiedRecently() throws Exception { String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String historyBody = "Some content"; - when(persistenceRepoMock.extractCommentsFromDedicatedIssue(isNull(), eq(topic), any())) + when(persistenceRepoMock.topic(TopicRef.history(topic))) + .thenReturn(topicMock); + when(topicMock.extractComments(any())) .thenAnswer(ignored -> Stream.of(historyBody)); when(messageFormatterMock.extractPayloadFromHistoryBodyMarkdown(historyBody)) .thenReturn(List.of( @@ -207,7 +216,9 @@ void lastNotificationTimedOutForIssueNumber_noHistory() throws Exception { String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); - when(persistenceRepoMock.extractCommentsFromDedicatedIssue(isNull(), eq(topic), any())) + when(persistenceRepoMock.topic(TopicRef.history(topic))) + .thenReturn(topicMock); + when(topicMock.extractComments(any())) .thenAnswer(ignored -> Stream.of()); var history = historyService.fetch(drawRef, config); @@ -229,7 +240,9 @@ void lastNotificationTimedOutForIssueNumber() throws Exception { String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String historyBody = "Some content"; - when(persistenceRepoMock.extractCommentsFromDedicatedIssue(isNull(), eq(topic), any())) + when(persistenceRepoMock.topic(TopicRef.history(topic))) + .thenReturn(topicMock); + when(topicMock.extractComments(any())) .thenAnswer(ignored -> Stream.of(historyBody)); when(messageFormatterMock.extractPayloadFromHistoryBodyMarkdown(historyBody)) .thenReturn(List.of( diff --git a/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java b/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java index d8ab599..b9aa502 100644 --- a/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java +++ b/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java @@ -141,7 +141,7 @@ void setup() throws IOException { private void mockNotifiable(String username, ZoneId timezone) throws IOException { when(historyMock.lastNotificationToday(username, timezone)).thenReturn(Optional.empty()); - when(notifierMock.hasClosedDedicatedIssue(username)).thenReturn(false); + when(notifierMock.isIgnoring(username)).thenReturn(false); } @Test @@ -240,7 +240,7 @@ void alreadyNotifiedToday() throws IOException { } @Test - void closedDedicatedIssue() throws IOException { + void ignoring() throws IOException { var config = defaultConfig(List.of( new LotteryConfig.Participant("yrodiere", Optional.empty(), @@ -261,7 +261,7 @@ void closedDedicatedIssue() throws IOException { when(repoMock.ref()).thenReturn(repoRef); when(historyMock.lastNotificationToday("yrodiere", ZoneOffset.UTC)).thenReturn(Optional.empty()); - when(notifierMock.hasClosedDedicatedIssue("yrodiere")).thenReturn(true); + when(notifierMock.isIgnoring("yrodiere")).thenReturn(true); lotteryService.draw(); diff --git a/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java b/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java index 2accaad..ec19710 100644 --- a/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java @@ -11,6 +11,7 @@ import java.time.ZoneOffset; import java.util.Optional; +import io.quarkus.github.lottery.github.TopicRef; import jakarta.inject.Inject; import org.junit.jupiter.api.BeforeEach; @@ -70,6 +71,9 @@ void hasClosedDedicatedIssue() throws IOException { var notificationRepoRef = new GitHubRepositoryRef(installationRef, config.createIssues().repository()); when(gitHubServiceMock.repository(notificationRepoRef)).thenReturn(notificationRepoMock); + GitHubRepository.Topic notificationTopicMock = Mockito.mock(GitHubRepository.Topic.class); + when(notificationRepoMock.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus"))) + .thenReturn(notificationTopicMock); Notifier notifier = notificationService.notifier(drawRef, config); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); @@ -77,14 +81,14 @@ void hasClosedDedicatedIssue() throws IOException { when(messageFormatterMock.formatNotificationTopicText(drawRef, "yrodiere")) .thenReturn("yrodiere's report for quarkusio/quarkus"); - when(notificationRepoMock.hasClosedDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus")) + when(notificationTopicMock.isClosed()) .thenReturn(true); - assertThat(notifier.hasClosedDedicatedIssue("yrodiere")).isTrue(); + assertThat(notifier.isIgnoring("yrodiere")).isTrue(); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); - when(notificationRepoMock.hasClosedDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus")) + when(notificationTopicMock.isClosed()) .thenReturn(false); - assertThat(notifier.hasClosedDedicatedIssue("yrodiere")).isFalse(); + assertThat(notifier.isIgnoring("yrodiere")).isFalse(); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); } @@ -96,6 +100,15 @@ void send() throws IOException { var notificationRepoRef = new GitHubRepositoryRef(installationRef, config.createIssues().repository()); when(gitHubServiceMock.repository(notificationRepoRef)).thenReturn(notificationRepoMock); when(notificationRepoMock.ref()).thenReturn(notificationRepoRef); + GitHubRepository.Topic notificationTopicYrodiereMock = Mockito.mock(GitHubRepository.Topic.class); + GitHubRepository.Topic notificationTopicGsmetMock = Mockito.mock(GitHubRepository.Topic.class); + GitHubRepository.Topic notificationTopicGeoandMock = Mockito.mock(GitHubRepository.Topic.class); + when(notificationRepoMock.topic(TopicRef.notification("yrodiere", "yrodiere's report for quarkusio/quarkus"))) + .thenReturn(notificationTopicYrodiereMock); + when(notificationRepoMock.topic(TopicRef.notification("gsmet", "gsmet's report for quarkusio/quarkus"))) + .thenReturn(notificationTopicGsmetMock); + when(notificationRepoMock.topic(TopicRef.notification("geoand", "geoand's report for quarkusio/quarkus"))) + .thenReturn(notificationTopicGeoandMock); Notifier notifier = notificationService.notifier(drawRef, config); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); @@ -112,8 +125,7 @@ void send() throws IOException { when(messageFormatterMock.formatNotificationBodyMarkdown(lotteryReport1, notificationRepoRef)) .thenReturn(markdownNotification1); notifier.send(lotteryReport1); - verify(notificationRepoMock).commentOnDedicatedIssue("yrodiere", "yrodiere's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", markdownNotification1); + verify(notificationTopicYrodiereMock).comment(" (updated 2017-11-06T06:00:00Z)", markdownNotification1); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); var lotteryReport2 = new LotteryReport(drawRef, "gsmet", Optional.empty(), @@ -130,8 +142,7 @@ void send() throws IOException { when(messageFormatterMock.formatNotificationBodyMarkdown(lotteryReport2, notificationRepoRef)) .thenReturn(markdownNotification2); notifier.send(lotteryReport2); - verify(notificationRepoMock).commentOnDedicatedIssue("gsmet", "gsmet's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", markdownNotification2); + verify(notificationTopicGsmetMock).comment(" (updated 2017-11-06T06:00:00Z)", markdownNotification2); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); var lotteryReport3 = new LotteryReport(drawRef, "geoand", Optional.empty(), @@ -148,8 +159,7 @@ void send() throws IOException { when(messageFormatterMock.formatNotificationBodyMarkdown(lotteryReport3, notificationRepoRef)) .thenReturn(markdownNotification3); notifier.send(lotteryReport3); - verify(notificationRepoMock).commentOnDedicatedIssue("geoand", "geoand's report for quarkusio/quarkus", - " (updated 2017-11-06T06:00:00Z)", markdownNotification3); + verify(notificationTopicGeoandMock).comment(" (updated 2017-11-06T06:00:00Z)", markdownNotification3); verifyNoMoreInteractions(gitHubServiceMock, notificationRepoMock, messageFormatterMock); }