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 dfb6d59..8df87c7 100644 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java @@ -21,7 +21,7 @@ import org.kohsuke.github.GHIssueComment; import org.kohsuke.github.GHIssueCommentQueryBuilder; import org.kohsuke.github.GHIssueEvent; -import org.kohsuke.github.GHIssueQueryBuilder; +import org.kohsuke.github.GHIssueSearchBuilder; import org.kohsuke.github.GHIssueState; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHUser; @@ -94,6 +94,12 @@ private GHRepository repository() throws IOException { return repository; } + private GHIssueSearchBuilder searchIssues() { + return client().searchIssues() + .q(GitHubSearchClauses.repo(ref)) + .q(GitHubSearchClauses.isIssue()); + } + private DynamicGraphQLClient graphQLClient() { if (graphQLClient == null) { graphQLClient = clientProvider.getInstallationGraphQLClient(ref.installationRef().installationId()); @@ -111,17 +117,15 @@ public Optional fetchLotteryConfig() throws IOException { * * @param updatedBefore An instant; all returned issues must have been last updated before that instant. * @return A lazily populated stream of matching issues. - * @throws IOException In case of I/O failure. * @throws java.io.UncheckedIOException In case of I/O failure. */ - public Stream issuesLastUpdatedBefore(Instant updatedBefore) throws IOException { - return toStream(repository().queryIssues() - .state(GHIssueState.OPEN) - .sort(GHIssueQueryBuilder.Sort.UPDATED) - .direction(GHDirection.DESC) + public Stream issuesLastUpdatedBefore(Instant updatedBefore) { + return toStream(searchIssues() + .isOpen() + .q(GitHubSearchClauses.updatedBefore(updatedBefore)) + .sort(GHIssueSearchBuilder.Sort.UPDATED) + .order(GHDirection.DESC) .list()) - .filter(notPullRequest()) - .filter(updatedBefore(updatedBefore)) .map(toIssueRecord()); } @@ -131,17 +135,16 @@ public Stream issuesLastUpdatedBefore(Instant updatedBefore) throws IOExc * @param label A GitHub label; if non-null, all returned issues must have been assigned that label. * @param updatedBefore An instant; all returned issues must have been last updated before that instant. * @return A lazily populated stream of matching issues. - * @throws IOException In case of I/O failure. * @throws java.io.UncheckedIOException In case of I/O failure. */ - public Stream issuesWithLabelLastUpdatedBefore(String label, Instant updatedBefore) throws IOException { - return toStream(repository().queryIssues().label(label) - .state(GHIssueState.OPEN) - .sort(GHIssueQueryBuilder.Sort.UPDATED) - .direction(GHDirection.DESC) + public Stream issuesWithLabelLastUpdatedBefore(String label, Instant updatedBefore) { + return toStream(searchIssues() + .isOpen() + .q(GitHubSearchClauses.label(label)) + .q(GitHubSearchClauses.updatedBefore(updatedBefore)) + .sort(GHIssueSearchBuilder.Sort.UPDATED) + .order(GHDirection.DESC) .list()) - .filter(notPullRequest()) - .filter(updatedBefore(updatedBefore)) .map(toIssueRecord()); } @@ -155,35 +158,21 @@ public Stream issuesWithLabelLastUpdatedBefore(String label, Instant upda * This label is not relevant to determining the last action. * @param updatedBefore An instant; all returned issues must have been last updated before that instant. * @return A lazily populated stream of matching issues. - * @throws IOException In case of I/O failure. * @throws java.io.UncheckedIOException In case of I/O failure. */ public Stream issuesLastActedOnByAndLastUpdatedBefore(Set initialActionLabels, String filterLabel, - IssueActionSide lastActionSide, Instant updatedBefore) throws IOException { - var theRepository = repository(); - var streams = initialActionLabels.stream() - .map(initialActionLabel -> toStream(theRepository.queryIssues() - .label(initialActionLabel) - .label(filterLabel) - .state(GHIssueState.OPEN) - .sort(GHIssueQueryBuilder.Sort.UPDATED) - .direction(GHDirection.DESC) - .list()) - .filter(notPullRequest()) - .filter(updatedBefore(updatedBefore)) - .filter(uncheckedIO((GHIssue ghIssue) -> lastActionSide - .equals(lastActionSide(ghIssue, initialActionLabels)))::apply) - .map(toIssueRecord())) - .toList(); - return Streams.interleave(streams); - } - - private Predicate updatedBefore(Instant updatedBefore) { - return uncheckedIO((GHIssue ghIssue) -> ghIssue.getUpdatedAt().toInstant().isBefore(updatedBefore))::apply; - } - - private Predicate notPullRequest() { - return (GHIssue ghIssue) -> !ghIssue.isPullRequest(); + IssueActionSide lastActionSide, Instant updatedBefore) { + return toStream(searchIssues() + .isOpen() + .q(GitHubSearchClauses.anyLabel(initialActionLabels)) + .q(GitHubSearchClauses.label(filterLabel)) + .q(GitHubSearchClauses.updatedBefore(updatedBefore)) + .sort(GHIssueSearchBuilder.Sort.UPDATED) + .order(GHDirection.DESC) + .list()) + .filter(uncheckedIO((GHIssue ghIssue) -> lastActionSide + .equals(lastActionSide(ghIssue, initialActionLabels)))::apply) + .map(toIssueRecord()); } private IssueActionSide lastActionSide(GHIssue ghIssue, Set initialActionLabels) throws IOException { @@ -312,12 +301,12 @@ public void comment(String topicSuffix, String markdownBody) } private Stream getDedicatedIssues() throws IOException { - var builder = repository().queryIssues().creator(appLogin()); + var builder = searchIssues() + .q(GitHubSearchClauses.author(appLogin())); if (ref.assignee() != null) { - builder.assignee(ref.assignee()); + builder.q(GitHubSearchClauses.assignee(ref.assignee())); } - builder.state(GHIssueState.ALL); - return Streams.toStream(builder.list()) + return 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 diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubSearchClauses.java b/src/main/java/io/quarkus/github/lottery/github/GitHubSearchClauses.java new file mode 100644 index 0000000..70ef8b0 --- /dev/null +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubSearchClauses.java @@ -0,0 +1,39 @@ +package io.quarkus.github.lottery.github; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.Set; + +public final class GitHubSearchClauses { + + private GitHubSearchClauses() { + } + + public static String repo(GitHubRepositoryRef ref) { + return "repo:" + ref.repositoryName(); + } + + public static String isIssue() { + return "is:issue"; + } + + public static String anyLabel(Set labels) { + return label(String.join(",", labels)); + } + + public static String label(String label) { + return "label:" + label; + } + + public static String updatedBefore(Instant updatedBefore) { + return "updated:<" + updatedBefore.atOffset(ZoneOffset.UTC).toLocalDateTime().toString(); + } + + public static String author(String author) { + return "author:" + author; + } + + public static String assignee(String assignee) { + return "assignee:" + assignee; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ef445ce..0e8463a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,6 +13,7 @@ quarkus.info.enabled=true %dev.quarkus.scheduler.enabled=false %dev.quarkus.log.min-level=TRACE %dev.quarkus.log.category."io.quarkus.github.lottery".level=TRACE +%dev.quarkus.log.category."org.kohsuke.github.GitHubClient".level=TRACE %prod.quarkus.openshift.labels."app"=quarkus-github-lottery # Renew the SSL certificate automatically diff --git a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java index af53aa1..682e3d2 100644 --- a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java @@ -8,7 +8,6 @@ import static io.quarkus.github.lottery.util.MockHelper.mockIssueForNotification; import static io.quarkus.github.lottery.util.MockHelper.mockLabel; import static io.quarkus.github.lottery.util.MockHelper.mockPagedIterable; -import static io.quarkus.github.lottery.util.MockHelper.mockPullRequestForLotteryFilteredOutByRepository; import static io.quarkus.github.lottery.util.MockHelper.mockUserForInspectedComments; import static io.quarkus.github.lottery.util.MockHelper.stubIssueList; import static org.assertj.core.api.Assertions.assertThat; @@ -49,7 +48,7 @@ import org.kohsuke.github.GHIssueComment; import org.kohsuke.github.GHIssueCommentQueryBuilder; import org.kohsuke.github.GHIssueEvent; -import org.kohsuke.github.GHIssueQueryBuilder; +import org.kohsuke.github.GHIssueSearchBuilder; import org.kohsuke.github.GHIssueState; import org.kohsuke.github.GHPermissionType; import org.kohsuke.github.GHRepository; @@ -96,7 +95,7 @@ void setup() { void listRepositories() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { @@ -130,7 +129,7 @@ void listRepositories() throws IOException { .containsExactlyInAnyOrder(repoRef); }) .then().github(mocks -> { - verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -299,24 +298,20 @@ void issuesLastUpdatedBefore() throws IOException { Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); Instant cutoff = now.minus(1, ChronoUnit.DAYS); - Date beforeCutoff = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); - Date afterCutoff = Date.from(cutoff.plus(1, ChronoUnit.HOURS)); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); - - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); - var prMock = mockPullRequestForLotteryFilteredOutByRepository(mocks, 0); - var issue1Mock = mockIssueForLottery(mocks, 1, beforeCutoff); - var issue2Mock = mockIssueForLottery(mocks, 3, beforeCutoff); - var issue3Mock = mockIssueForLottery(mocks, 2, beforeCutoff); - var issue4Mock = mockIssueForLottery(mocks, 4, beforeCutoff); - var issue5Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 5, afterCutoff); - var issuesMocks = mockPagedIterable(prMock, issue1Mock, issue2Mock, issue3Mock, issue4Mock, issue5Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + var clientMock = mocks.installationClient(installationRef.installationId()); + + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); + var issue1Mock = mockIssueForLottery(mocks, 1); + var issue2Mock = mockIssueForLottery(mocks, 3); + var issue3Mock = mockIssueForLottery(mocks, 2); + var issue4Mock = mockIssueForLottery(mocks, 4); + var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock, issue3Mock, issue4Mock); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); }) .when(() -> { var repo = gitHubService.repository(repoRef); @@ -325,10 +320,13 @@ void issuesLastUpdatedBefore() throws IOException { .containsExactlyElementsOf(stubIssueList(1, 3, 2, 4)); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesBuilderMock).direction(GHDirection.DESC); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).isOpen(); + verify(searchIssuesBuilderMock).q("updated:<2017-11-05T06:00"); + verify(searchIssuesBuilderMock).sort(GHIssueSearchBuilder.Sort.UPDATED); + verify(searchIssuesBuilderMock).order(GHDirection.DESC); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -339,24 +337,20 @@ void issuesWithLabelLastUpdatedBefore() throws IOException { Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); Instant cutoff = now.minus(1, ChronoUnit.DAYS); - Date beforeCutoff = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); - Date afterCutoff = Date.from(cutoff.plus(1, ChronoUnit.HOURS)); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); - - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); - var prMock = mockPullRequestForLotteryFilteredOutByRepository(mocks, 0); - var issue1Mock = mockIssueForLottery(mocks, 1, beforeCutoff); - var issue2Mock = mockIssueForLottery(mocks, 3, beforeCutoff); - var issue3Mock = mockIssueForLottery(mocks, 2, beforeCutoff); - var issue4Mock = mockIssueForLottery(mocks, 4, beforeCutoff); - var issue5Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 5, afterCutoff); - var issuesMocks = mockPagedIterable(prMock, issue1Mock, issue2Mock, issue3Mock, issue4Mock, issue5Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + var clientMock = mocks.installationClient(installationRef.installationId()); + + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); + var issue1Mock = mockIssueForLottery(mocks, 1); + var issue2Mock = mockIssueForLottery(mocks, 3); + var issue3Mock = mockIssueForLottery(mocks, 2); + var issue4Mock = mockIssueForLottery(mocks, 4); + var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock, issue3Mock, issue4Mock); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); }) .when(() -> { var repo = gitHubService.repository(repoRef); @@ -365,11 +359,14 @@ void issuesWithLabelLastUpdatedBefore() throws IOException { .containsExactlyElementsOf(stubIssueList(1, 3, 2, 4)); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesBuilderMock).direction(GHDirection.DESC); - verify(queryIssuesBuilderMock).label("triage/needs-triage"); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).isOpen(); + verify(searchIssuesBuilderMock).sort(GHIssueSearchBuilder.Sort.UPDATED); + verify(searchIssuesBuilderMock).order(GHDirection.DESC); + verify(searchIssuesBuilderMock).q("label:triage/needs-triage"); + verify(searchIssuesBuilderMock).q("updated:<2017-11-05T06:00"); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -380,16 +377,12 @@ void issuesLastActedOnByAndLastUpdatedBefore_team() throws IOException { Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); Instant cutoff = now.minus(1, ChronoUnit.DAYS); - Date beforeCutoff = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); - Date afterCutoff = Date.from(cutoff.plus(1, ChronoUnit.HOURS)); Date issue1ActionLabelEvent = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); Date issue2ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); Date issue7ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); Date issue8ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); - var queryIssuesNeedsFeedbackBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, - withSettings().defaultAnswer(Answers.RETURNS_SELF)); - var queryIssuesNeedsReproducerBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var issue1QueryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); @@ -407,6 +400,7 @@ void issuesLastActedOnByAndLastUpdatedBefore_team() throws IOException { withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { + var clientMock = mocks.installationClient(installationRef.installationId()); var repositoryMock = mocks.repository(repoRef.repositoryName()); var adminUser = mockUserForInspectedComments(mocks, repositoryMock, 1L, "someadmin", @@ -419,22 +413,21 @@ void issuesLastActedOnByAndLastUpdatedBefore_team() throws IOException { var botUser = mockUserForInspectedComments(mocks, repositoryMock, 5L, "somebot[bot]"); var randomReporterUser = mockUserForInspectedComments(mocks, repositoryMock, 6L, "somereporter"); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesNeedsFeedbackBuilderMock) - .thenReturn(queryIssuesNeedsReproducerBuilderMock); - var prMock = mockPullRequestForLotteryFilteredOutByRepository(mocks, 0); - var issue1Mock = mockIssueForLottery(mocks, 1, beforeCutoff, randomReporterUser); - var issue2Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 2, beforeCutoff, randomReporterUser); - var issue3Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 3, beforeCutoff, randomReporterUser); - var issue4Mock = mockIssueForLottery(mocks, 4, beforeCutoff); - var issue5Mock = mockIssueForLottery(mocks, 5, beforeCutoff, randomReporterUser); - var issue6Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 6, afterCutoff); - var issue7Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 7, beforeCutoff, randomReporterUser); - var issue8Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 8, beforeCutoff, writeUser); - var issuesNeedsFeedbackMocks = mockPagedIterable(issue1Mock, issue3Mock, issue5Mock); - when(queryIssuesNeedsFeedbackBuilderMock.list()).thenReturn(issuesNeedsFeedbackMocks); - var issuesNeedsReproducerMocks = mockPagedIterable(prMock, issue2Mock, issue4Mock, issue6Mock, issue7Mock, - issue8Mock); - when(queryIssuesNeedsReproducerBuilderMock.list()).thenReturn(issuesNeedsReproducerMocks); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock) + .thenReturn(searchIssuesBuilderMock); + var issue1Mock = mockIssueForLottery(mocks, 1, randomReporterUser); + var issue2Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 2, randomReporterUser); + var issue3Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 3, randomReporterUser); + var issue4Mock = mockIssueForLottery(mocks, 4); + var issue5Mock = mockIssueForLottery(mocks, 5, randomReporterUser); + // issue6 used to be one after the cutoff, filtered out on the client side, + // but that's handled server-side now. + // Keeping this gap in numbering to avoid an even worse diff. + var issue7Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 7, randomReporterUser); + var issue8Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 8, writeUser); + var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock, issue3Mock, issue4Mock, issue5Mock, + issue7Mock, issue8Mock); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); var needsReproducerLabelMock = mockLabel("triage/needs-reproducer"); var areaHibernateSearchLabelMock = mockLabel("area/hibernate-search"); @@ -533,17 +526,12 @@ void issuesLastActedOnByAndLastUpdatedBefore_team() throws IOException { .containsExactlyElementsOf(stubIssueList(1, 4, 5)); }) .then().github(mocks -> { - verify(queryIssuesNeedsFeedbackBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesNeedsFeedbackBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesNeedsFeedbackBuilderMock).direction(GHDirection.DESC); - verify(queryIssuesNeedsFeedbackBuilderMock).label("triage/needs-feedback"); - verify(queryIssuesNeedsFeedbackBuilderMock).label("area/hibernate-search"); - verify(queryIssuesNeedsReproducerBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesNeedsReproducerBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesNeedsReproducerBuilderMock).direction(GHDirection.DESC); - verify(queryIssuesNeedsReproducerBuilderMock).label("triage/needs-reproducer"); - verify(queryIssuesNeedsReproducerBuilderMock).label("area/hibernate-search"); - verifyNoMoreInteractions(queryIssuesNeedsReproducerBuilderMock, queryIssuesNeedsFeedbackBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).isOpen(); + verify(searchIssuesBuilderMock).sort(GHIssueSearchBuilder.Sort.UPDATED); + verify(searchIssuesBuilderMock).order(GHDirection.DESC); + verify(searchIssuesBuilderMock).q("label:triage/needs-feedback,triage/needs-reproducer"); + verify(searchIssuesBuilderMock).q("label:area/hibernate-search"); verify(issue1QueryCommentsBuilderMock).since(issue1ActionLabelEvent); verify(issue2QueryCommentsBuilderMock).since(issue2ActionLabelEvent); @@ -560,16 +548,12 @@ void issuesLastActedOnByAndLastUpdatedBefore_outsider() throws IOException { Instant now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); Instant cutoff = now.minus(1, ChronoUnit.DAYS); - Date beforeCutoff = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); - Date afterCutoff = Date.from(cutoff.plus(1, ChronoUnit.HOURS)); Date issue1ActionLabelEvent = Date.from(cutoff.minus(1, ChronoUnit.DAYS)); Date issue2ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); Date issue7ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); Date issue8ActionLabelEvent = Date.from(cutoff.minus(2, ChronoUnit.DAYS)); - var queryIssuesNeedsReproducerBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, - withSettings().defaultAnswer(Answers.RETURNS_SELF)); - var queryIssuesNeedsFeedbackBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var issue1QueryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); @@ -587,6 +571,7 @@ void issuesLastActedOnByAndLastUpdatedBefore_outsider() throws IOException { withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { + var clientMock = mocks.installationClient(installationRef.installationId()); var repositoryMock = mocks.repository(repoRef.repositoryName()); var adminUser = mockUserForInspectedComments(mocks, repositoryMock, 1L, "someadmin", @@ -599,24 +584,21 @@ void issuesLastActedOnByAndLastUpdatedBefore_outsider() throws IOException { var botUser = mockUserForInspectedComments(mocks, repositoryMock, 5L, "somebot[bot]"); var randomReporterUser = mockUserForInspectedComments(mocks, repositoryMock, 6L, "somereporter"); - when(repositoryMock.queryIssues()) - .thenReturn(queryIssuesNeedsFeedbackBuilderMock) - .thenReturn(queryIssuesNeedsReproducerBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); // Pull requests should always be filtered out - var prMock = mockPullRequestForLotteryFilteredOutByRepository(mocks, 0); - var issue1Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 1, beforeCutoff, randomReporterUser); - var issue2Mock = mockIssueForLottery(mocks, 2, beforeCutoff, randomReporterUser); - var issue3Mock = mockIssueForLottery(mocks, 3, beforeCutoff, randomReporterUser); - var issue4Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 4, beforeCutoff); - var issue5Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 5, beforeCutoff, randomReporterUser); - var issue6Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 6, afterCutoff); - var issue7Mock = mockIssueForLottery(mocks, 7, beforeCutoff, randomReporterUser); - var issue8Mock = mockIssueForLottery(mocks, 8, beforeCutoff, writeUser); - var issuesNeedsFeedbackMocks = mockPagedIterable(prMock, issue2Mock, issue4Mock, issue6Mock, issue7Mock, - issue8Mock); - when(queryIssuesNeedsFeedbackBuilderMock.list()).thenReturn(issuesNeedsFeedbackMocks); - var issuesNeedsReproducerMocks = mockPagedIterable(issue1Mock, issue3Mock, issue5Mock); - when(queryIssuesNeedsReproducerBuilderMock.list()).thenReturn(issuesNeedsReproducerMocks); + var issue1Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 1, randomReporterUser); + var issue2Mock = mockIssueForLottery(mocks, 2, randomReporterUser); + var issue3Mock = mockIssueForLottery(mocks, 3, randomReporterUser); + var issue4Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 4); + var issue5Mock = mockIssueForLotteryFilteredOutByRepository(mocks, 5, randomReporterUser); + // issue6 used to be one after the cutoff, filtered out on the client side, + // but that's handled server-side now. + // Keeping this gap in numbering to avoid an even worse diff. + var issue7Mock = mockIssueForLottery(mocks, 7, randomReporterUser); + var issue8Mock = mockIssueForLottery(mocks, 8, writeUser); + var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock, issue3Mock, issue4Mock, issue5Mock, + issue7Mock, issue8Mock); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); var needsReproducerLabelMock = mockLabel("triage/needs-reproducer"); var areaHibernateSearchLabelMock = mockLabel("area/hibernate-search"); @@ -715,17 +697,12 @@ void issuesLastActedOnByAndLastUpdatedBefore_outsider() throws IOException { .containsExactlyElementsOf(stubIssueList(2, 3, 7, 8)); }) .then().github(mocks -> { - verify(queryIssuesNeedsFeedbackBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesNeedsFeedbackBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesNeedsFeedbackBuilderMock).direction(GHDirection.DESC); - verify(queryIssuesNeedsFeedbackBuilderMock).label("triage/needs-feedback"); - verify(queryIssuesNeedsFeedbackBuilderMock).label("area/hibernate-search"); - verify(queryIssuesNeedsReproducerBuilderMock).state(GHIssueState.OPEN); - verify(queryIssuesNeedsReproducerBuilderMock).sort(GHIssueQueryBuilder.Sort.UPDATED); - verify(queryIssuesNeedsReproducerBuilderMock).direction(GHDirection.DESC); - verify(queryIssuesNeedsReproducerBuilderMock).label("triage/needs-reproducer"); - verify(queryIssuesNeedsReproducerBuilderMock).label("area/hibernate-search"); - verifyNoMoreInteractions(queryIssuesNeedsReproducerBuilderMock, queryIssuesNeedsFeedbackBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).isOpen(); + verify(searchIssuesBuilderMock).sort(GHIssueSearchBuilder.Sort.UPDATED); + verify(searchIssuesBuilderMock).order(GHDirection.DESC); + verify(searchIssuesBuilderMock).q("label:triage/needs-feedback,triage/needs-reproducer"); + verify(searchIssuesBuilderMock).q("label:area/hibernate-search"); verify(issue1QueryCommentsBuilderMock).since(issue1ActionLabelEvent); verify(issue2QueryCommentsBuilderMock).since(issue2ActionLabelEvent); @@ -741,18 +718,18 @@ 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); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "Another unrelated issue"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); }) .when(() -> { var repo = gitHubService.repository(repoRef); @@ -762,9 +739,10 @@ void topic_extractComments_dedicatedIssueDoesNotExist() throws Exception { .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -774,18 +752,19 @@ void topic_extractComments_dedicatedIssueDoesNotExist_withConfusingOther() throw 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, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()) + .thenReturn(searchIssuesBuilderMock); 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(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); }) .when(() -> { var repo = gitHubService.repository(repoRef); @@ -795,9 +774,10 @@ void topic_extractComments_dedicatedIssueDoesNotExist_withConfusingOther() throw .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -807,20 +787,20 @@ void topic_extractComments_dedicatedIssueExists_appCommentsDoNotExist() throws E 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, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); var someoneElseMock = mocks.ghObject(GHUser.class, 2L); when(someoneElseMock.getLogin()).thenReturn("yrodiere"); @@ -838,11 +818,12 @@ void topic_extractComments_dedicatedIssueExists_appCommentsDoNotExist() throws E .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(since)); - verifyNoMoreInteractions(queryIssuesBuilderMock, queryCommentsBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock, queryCommentsBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -852,20 +833,20 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist_allTooOld() thr 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, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.queryComments()).thenReturn(queryCommentsBuilderMock); PagedSearchIterable issue2CommentMocks = mockPagedIterable(); @@ -879,11 +860,12 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist_allTooOld() thr .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(since)); - verifyNoMoreInteractions(queryIssuesBuilderMock, queryCommentsBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock, queryCommentsBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -893,20 +875,20 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist() throws Except 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, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); var mySelfMock = mocks.ghObject(GHUser.class, 1L); when(mySelfMock.getLogin()).thenReturn(installationRef.appLogin()); @@ -928,11 +910,12 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist() throws Except .containsExactly("issue2Comment1Mock#body", "issue2Comment2Mock#body"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(since)); - verifyNoMoreInteractions(queryIssuesBuilderMock, queryCommentsBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock, queryCommentsBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -942,20 +925,20 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist_withConfusingOt 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, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "Lottery history for quarkusio/quarkusio.github.io"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); var mySelfMock = mocks.ghObject(GHUser.class, 1L); when(mySelfMock.getLogin()).thenReturn(installationRef.appLogin()); @@ -977,11 +960,12 @@ void topic_extractComments_dedicatedIssueExists_appCommentsExist_withConfusingOt .containsExactly("issue2Comment1Mock#body", "issue2Comment2Mock#body"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(since)); - verifyNoMoreInteractions(queryIssuesBuilderMock, queryCommentsBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock, queryCommentsBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -996,21 +980,21 @@ void topic_comment_dedicatedIssueExists_open() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "yrodiere's report for quarkusio/quarkus (updated 2017-11-05T06:00:00Z)"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.OPEN); @@ -1038,9 +1022,10 @@ void topic_comment_dedicatedIssueExists_open() throws Exception { .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); verify(queryCommentsBuilderMock).since(Date.from(now.minus(21, ChronoUnit.DAYS))); var mapCaptor = ArgumentCaptor.forClass(Map.class); @@ -1051,7 +1036,7 @@ void topic_comment_dedicatedIssueExists_open() throws Exception { verify(mocks.issue(2)).setBody("Dedicated issue body"); verify(mocks.issue(2)).comment("Some content"); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); assertThat(mapCaptor.getValue()).containsValue(commentToMinimizeNodeId); @@ -1068,20 +1053,20 @@ void topic_comment_dedicatedIssueExists_noTopicSuffix() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.OPEN); @@ -1109,8 +1094,9 @@ void topic_comment_dedicatedIssueExists_noTopicSuffix() throws Exception { .comment("", "Some content"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(now.minus(21, ChronoUnit.DAYS))); var mapCaptor = ArgumentCaptor.forClass(Map.class); @@ -1120,7 +1106,7 @@ void topic_comment_dedicatedIssueExists_noTopicSuffix() throws Exception { verify(mocks.issue(2)).setBody("Dedicated issue body"); verify(mocks.issue(2)).comment("Some content"); - verifyNoMoreInteractions(messageFormatterMock, queryIssuesBuilderMock); + verifyNoMoreInteractions(messageFormatterMock, searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); assertThat(mapCaptor.getValue()).containsValue(commentToMinimizeNodeId); @@ -1137,20 +1123,20 @@ void topic_comment_dedicatedIssueExists_withConfusingOther() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "Lottery history for quarkusio/quarkusio.github.io"); var issue2Mock = mockIssueForNotification(mocks, 2, "Lottery history for quarkusio/quarkus"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.OPEN); @@ -1178,8 +1164,9 @@ void topic_comment_dedicatedIssueExists_withConfusingOther() throws Exception { .comment("", "Some content"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); verify(queryCommentsBuilderMock).since(Date.from(now.minus(21, ChronoUnit.DAYS))); var mapCaptor = ArgumentCaptor.forClass(Map.class); @@ -1189,7 +1176,7 @@ void topic_comment_dedicatedIssueExists_withConfusingOther() throws Exception { verify(mocks.issue(2)).setBody("Dedicated issue body"); verify(mocks.issue(2)).comment("Some content"); - verifyNoMoreInteractions(messageFormatterMock, queryIssuesBuilderMock); + verifyNoMoreInteractions(messageFormatterMock, searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); assertThat(mapCaptor.getValue()).containsValue(commentToMinimizeNodeId); @@ -1206,21 +1193,21 @@ void topic_comment_dedicatedIssueExists_closed() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var queryCommentsBuilderMock = Mockito.mock(GHIssueCommentQueryBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "yrodiere's report for quarkusio/quarkus (updated 2017-11-05T06:00:00Z)"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.CLOSED); @@ -1248,9 +1235,10 @@ void topic_comment_dedicatedIssueExists_closed() throws Exception { .comment(" (updated 2017-11-06T06:00:00Z)", "Some content"); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); verify(mocks.issue(2)).setTitle("yrodiere's report for quarkusio/quarkus (updated 2017-11-06T06:00:00Z)"); verify(mocks.issue(2)).reopen(); @@ -1263,7 +1251,7 @@ void topic_comment_dedicatedIssueExists_closed() throws Exception { verify(mocks.issue(2)).setBody("Dedicated issue body"); verify(mocks.issue(2)).comment("Some content"); - verifyNoMoreInteractions(messageFormatterMock, queryIssuesBuilderMock); + verifyNoMoreInteractions(messageFormatterMock, searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); assertThat(mapCaptor.getValue()).containsValue(commentToMinimizeNodeId); @@ -1273,19 +1261,20 @@ void topic_comment_dedicatedIssueExists_closed() throws Exception { @Test void topic_comment_dedicatedIssueDoesNotExist() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var issueBuilderMock = Mockito.mock(GHIssueBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { + var clientMock = mocks.installationClient(installationRef.installationId()); var repositoryMock = mocks.repository(repoRef.repositoryName()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issuesMocks = mockPagedIterable(issue1Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(repositoryMock.createIssue(any())).thenReturn(issueBuilderMock); var issue2Mock = mocks.issue(2); @@ -1304,16 +1293,17 @@ void topic_comment_dedicatedIssueDoesNotExist() throws IOException { .then().github(mocks -> { var repositoryMock = mocks.repository(repoRef.repositoryName()); - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); 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(messageFormatterMock, searchIssuesBuilderMock, issueBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -1321,19 +1311,20 @@ void topic_comment_dedicatedIssueDoesNotExist() throws IOException { @Test void topic_comment_dedicatedIssueDoesNotExist_withConfusingOther() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); var issueBuilderMock = Mockito.mock(GHIssueBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { + var clientMock = mocks.installationClient(installationRef.installationId()); var repositoryMock = mocks.repository(repoRef.repositoryName()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "yrodiere's report for quarkusio/quarkusio.githbub.io"); var issuesMocks = mockPagedIterable(issue1Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(repositoryMock.createIssue(any())).thenReturn(issueBuilderMock); var issue2Mock = mocks.issue(2); @@ -1352,16 +1343,17 @@ void topic_comment_dedicatedIssueDoesNotExist_withConfusingOther() throws IOExce .then().github(mocks -> { var repositoryMock = mocks.repository(repoRef.repositoryName()); - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); 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(messageFormatterMock, searchIssuesBuilderMock, issueBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -1374,19 +1366,19 @@ void topic_isClosed_dedicatedIssueExists_open() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "yrodiere's report for quarkusio/quarkus (updated 2017-11-05T06:00:00Z)"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.OPEN); }) @@ -1398,11 +1390,12 @@ void topic_isClosed_dedicatedIssueExists_open() throws Exception { .isFalse(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -1415,19 +1408,19 @@ void topic_isClosed_dedicatedIssueExists_closed() throws Exception { var clockMock = Clock.fixed(now, ZoneOffset.UTC); QuarkusMock.installMockForType(clockMock, Clock.class); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issue2Mock = mockIssueForNotification(mocks, 2, "yrodiere's report for quarkusio/quarkus (updated 2017-11-05T06:00:00Z)"); var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); when(issue2Mock.getState()).thenReturn(GHIssueState.CLOSED); }) @@ -1439,11 +1432,12 @@ void topic_isClosed_dedicatedIssueExists_closed() throws Exception { .isTrue(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } @@ -1451,17 +1445,17 @@ void topic_isClosed_dedicatedIssueExists_closed() throws Exception { @Test void topic_isClosed_dedicatedIssueDoesNotExist() throws IOException { var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); - var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, + var searchIssuesBuilderMock = Mockito.mock(GHIssueSearchBuilder.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { - var repositoryMock = mocks.repository(repoRef.repositoryName()); + var clientMock = mocks.installationClient(installationRef.installationId()); - when(repositoryMock.queryIssues()).thenReturn(queryIssuesBuilderMock); + when(clientMock.searchIssues()).thenReturn(searchIssuesBuilderMock); var issue1Mock = mockIssueForNotification(mocks, 1, "An unrelated issue"); var issuesMocks = mockPagedIterable(issue1Mock); - when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); + when(searchIssuesBuilderMock.list()).thenReturn(issuesMocks); }) .when(() -> { var repo = gitHubService.repository(repoRef); @@ -1471,11 +1465,12 @@ void topic_isClosed_dedicatedIssueDoesNotExist() throws IOException { .isFalse(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).creator(installationRef.appLogin()); - verify(queryIssuesBuilderMock).assignee("yrodiere"); - verify(queryIssuesBuilderMock).state(GHIssueState.ALL); + verify(searchIssuesBuilderMock).q("repo:" + repoRef.repositoryName()); + verify(searchIssuesBuilderMock).q("is:issue"); + verify(searchIssuesBuilderMock).q("author:" + installationRef.appLogin()); + verify(searchIssuesBuilderMock).q("assignee:yrodiere"); - verifyNoMoreInteractions(queryIssuesBuilderMock); + verifyNoMoreInteractions(searchIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); }); } diff --git a/src/test/java/io/quarkus/github/lottery/util/MockHelper.java b/src/test/java/io/quarkus/github/lottery/util/MockHelper.java index 2ea16c0..9a56fc4 100644 --- a/src/test/java/io/quarkus/github/lottery/util/MockHelper.java +++ b/src/test/java/io/quarkus/github/lottery/util/MockHelper.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.stream.IntStream; @@ -18,7 +17,6 @@ import org.kohsuke.github.GHIssueEvent; import org.kohsuke.github.GHLabel; import org.kohsuke.github.GHPermissionType; -import org.kohsuke.github.GHPullRequest; import org.kohsuke.github.GHPullRequestFileDetail; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHUser; @@ -65,49 +63,35 @@ private static Issue stubIssue(int number) { return new Issue(number, "Title for issue " + number, url(number)); } - public static GHIssue mockIssueForLottery(GitHubMockContext context, int number, Date updatedAt) + public static GHIssue mockIssueForLottery(GitHubMockContext context, int number) throws IOException { GHIssue mock = context.issue(10000L + number); - when(mock.isPullRequest()).thenReturn(false); when(mock.getNumber()).thenReturn(number); when(mock.getTitle()).thenReturn("Title for issue " + number); when(mock.getHtmlUrl()).thenReturn(url(number)); - when(mock.getUpdatedAt()).thenReturn(updatedAt); return mock; } - public static GHIssue mockIssueForLottery(GitHubMockContext context, int number, Date updatedAt, GHUser reporter) + public static GHIssue mockIssueForLottery(GitHubMockContext context, int number, GHUser reporter) throws IOException { GHIssue mock = context.issue(10000L + number); - when(mock.isPullRequest()).thenReturn(false); when(mock.getNumber()).thenReturn(number); when(mock.getTitle()).thenReturn("Title for issue " + number); when(mock.getHtmlUrl()).thenReturn(url(number)); - when(mock.getUpdatedAt()).thenReturn(updatedAt); when(mock.getUser()).thenReturn(reporter); return mock; } - public static GHIssue mockIssueForLotteryFilteredOutByRepository(GitHubMockContext context, int number, Date updatedAt) + public static GHIssue mockIssueForLotteryFilteredOutByRepository(GitHubMockContext context, int number) throws IOException { GHIssue mock = context.issue(10000L + number); - when(mock.isPullRequest()).thenReturn(false); - when(mock.getUpdatedAt()).thenReturn(updatedAt); return mock; } - public static GHPullRequest mockPullRequestForLotteryFilteredOutByRepository(GitHubMockContext context, int number) { - GHPullRequest mock = context.pullRequest(10000L + number); - when(mock.isPullRequest()).thenReturn(true); - return mock; - } - - public static GHIssue mockIssueForLotteryFilteredOutByRepository(GitHubMockContext context, int number, Date updatedAt, + public static GHIssue mockIssueForLotteryFilteredOutByRepository(GitHubMockContext context, int number, GHUser reporter) throws IOException { GHIssue mock = context.issue(10000L + number); - when(mock.isPullRequest()).thenReturn(false); - when(mock.getUpdatedAt()).thenReturn(updatedAt); when(mock.getUser()).thenReturn(reporter); return mock; }