diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff713bc..d8919ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,6 +49,12 @@ jobs: # refresh cache every month to avoid unlimited growth key: maven-repo-pr-${{ runner.os }}-${{ steps.get-date.outputs.date }} + - name: Check out yrodiere:github-api i1082-listRepositories + run: git clone -b i1082-listRepositories https://github.com/yrodiere/github-api.git + + - name: Build github-api SNAPSHOT + run: cd github-api && mvn -B clean install -DskipTests && cd - + - name: Validate formatting run: mvn -B clean formatter:validate diff --git a/pom.xml b/pom.xml index 64af735..6eece9b 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,11 @@ pom import + + org.kohsuke + github-api + 1.309-SNAPSHOT + @@ -71,10 +76,16 @@ quarkus-github-app ${quarkus-github-app.version} + + io.quarkiverse.githubapp + quarkus-github-app-command-airline + ${quarkus-github-app.version} + io.quarkiverse.githubapp quarkus-github-app-testing ${quarkus-github-app.version} + test diff --git a/src/main/java/io/quarkus/github/lottery/LotteryCli.java b/src/main/java/io/quarkus/github/lottery/LotteryCli.java new file mode 100644 index 0000000..b716040 --- /dev/null +++ b/src/main/java/io/quarkus/github/lottery/LotteryCli.java @@ -0,0 +1,30 @@ +package io.quarkus.github.lottery; + +import java.io.IOException; + +import org.kohsuke.github.GHPermissionType; + +import com.github.rvesse.airline.annotations.Cli; +import com.github.rvesse.airline.annotations.Command; + +import io.quarkiverse.githubapp.command.airline.Permission; +import io.quarkus.arc.Arc; + +@Cli(name = "/lottery", commands = { LotteryCli.DrawCommand.class }) +public class LotteryCli { + + interface Commands { + void run() throws IOException; + } + + @Command(name = "draw") + @Permission(GHPermissionType.ADMIN) + static class DrawCommand implements Commands { + @Override + public void run() throws IOException { + // Cannot inject the service for some reason, + // as Airline uses reflection and performs calls to setAccessible recursively. + Arc.container().instance(LotteryService.class).get().draw(); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/quarkus/github/lottery/LotteryService.java b/src/main/java/io/quarkus/github/lottery/LotteryService.java index 3794cd8..1009b79 100644 --- a/src/main/java/io/quarkus/github/lottery/LotteryService.java +++ b/src/main/java/io/quarkus/github/lottery/LotteryService.java @@ -47,8 +47,8 @@ public class LotteryService { /** * Draws the lottery and sends lists of tickets to participants as necessary. */ - @Scheduled(cron = "0 0 * ? * *") // Every hour - public void draw() throws IOException { + @Scheduled(every = "1H", concurrentExecution = Scheduled.ConcurrentExecution.SKIP) // Every hour + public synchronized void draw() throws IOException { Log.info("Starting draw..."); List refs = gitHubService.listRepositories(); Log.infof("Will draw for the following repositories: %s", refs); diff --git a/src/main/java/io/quarkus/github/lottery/config/LotteryConfig.java b/src/main/java/io/quarkus/github/lottery/config/LotteryConfig.java index 8e0f486..bb6d66c 100644 --- a/src/main/java/io/quarkus/github/lottery/config/LotteryConfig.java +++ b/src/main/java/io/quarkus/github/lottery/config/LotteryConfig.java @@ -16,7 +16,7 @@ public record LotteryConfig( @JsonProperty(required = true) BucketsConfig buckets, List participants) { - public static final String FILE_NAME = "quarkus-github-lottery.yaml"; + public static final String FILE_NAME = "quarkus-github-lottery.yml"; public record BucketsConfig( @JsonProperty(required = true) TriageBucketConfig triage) { diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubInstallationRef.java b/src/main/java/io/quarkus/github/lottery/github/GitHubInstallationRef.java new file mode 100644 index 0000000..0fed9e0 --- /dev/null +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubInstallationRef.java @@ -0,0 +1,10 @@ +package io.quarkus.github.lottery.github; + +/** + * A reference to a GitHub application installation. + * + * @param appName The application name. + * @param installationId The installation ID. + */ +public record GitHubInstallationRef(String appName, long installationId) { +} 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 896e5c0..bc6b7bc 100644 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubRepository.java @@ -61,13 +61,13 @@ public GitHubRepositoryRef ref() { return ref; } - public String selfUsername() throws IOException { - return client().getMyself().getLogin(); + public String selfLogin() { + return ref.installationRef().appName() + "[bot]"; } private GitHub client() { if (client == null) { - client = clientProvider.getInstallationClient(ref.installationId()); + client = clientProvider.getInstallationClient(ref.installationRef().installationId()); } return client; } @@ -81,7 +81,7 @@ private GHRepository repository() throws IOException { private DynamicGraphQLClient graphQLClient() { if (graphQLClient == null) { - graphQLClient = clientProvider.getInstallationGraphQLClient(ref.installationId()); + graphQLClient = clientProvider.getInstallationGraphQLClient(ref.installationRef().installationId()); } return graphQLClient; } @@ -120,9 +120,9 @@ public void commentOnDedicatedIssue(String username, String topic, String markdo issue.comment(markdownBody); } - public Stream extractCommentsFromDedicatedIssue(String username, String topic, Instant since) + public Stream extractCommentsFromDedicatedIssue(String login, String topic, Instant since) throws IOException { - return getDedicatedIssue(username, topic) + return getDedicatedIssue(login, topic) .map(uncheckedIO(issue -> getAppCommentsSince(issue, since))) .orElse(Stream.of()) .map(GHIssueComment::getBody); @@ -145,14 +145,14 @@ private GHIssue createDedicatedIssue(String username, String topic) throws IOExc } private Optional getLastAppComment(GHIssue issue) throws IOException { - long selfId = client().getMyself().getId(); + String selfLogin = selfLogin(); // TODO ideally we'd use the "since" API parameter to ignore // older comments (e.g. 1+ year old) that are unlikely to be relevant // (see 'since' in https://docs.github.com/en/rest/issues/comments#list-issue-comments) // but that's not supported yet in the library we're using... GHIssueComment lastNotificationComment = null; for (GHIssueComment comment : issue.listComments()) { - if (selfId == comment.getUser().getId()) { + if (selfLogin.equals(comment.getUser().getLogin())) { lastNotificationComment = comment; } } @@ -160,12 +160,12 @@ private Optional getLastAppComment(GHIssue issue) throws IOExcep } private Stream getAppCommentsSince(GHIssue issue, Instant since) throws IOException { - long selfId = client().getMyself().getId(); + String selfLogin = selfLogin(); // TODO ideally we'd use the "since" API parameter to ignore older comments // (see 'since' in https://docs.github.com/en/rest/issues/comments#list-issue-comments) // but that's not supported yet in the library we're using... return toStream(issue.listComments()) - .filter(uncheckedIO((GHIssueComment comment) -> selfId == comment.getUser().getId() + .filter(uncheckedIO((GHIssueComment comment) -> selfLogin.equals(comment.getUser().getLogin()) && !comment.getCreatedAt().toInstant().isBefore(since))::apply); } diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubRepositoryRef.java b/src/main/java/io/quarkus/github/lottery/github/GitHubRepositoryRef.java index 8fb8d5b..778e5b4 100644 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubRepositoryRef.java +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubRepositoryRef.java @@ -3,13 +3,13 @@ /** * A reference to a GitHub repository as viewed from a GitHub App installation. * - * @param installationId The installation ID. + * @param installationRef A reference to the GitHub installation. * @param repositoryName The full name of the GitHub repository. */ -public record GitHubRepositoryRef(long installationId, String repositoryName) { +public record GitHubRepositoryRef(GitHubInstallationRef installationRef, String repositoryName) { @Override public String toString() { - return repositoryName + "(through installation " + installationId + ")"; + return repositoryName + "(through " + installationRef + ")"; } } diff --git a/src/main/java/io/quarkus/github/lottery/github/GitHubService.java b/src/main/java/io/quarkus/github/lottery/github/GitHubService.java index 90dcd36..271b57b 100644 --- a/src/main/java/io/quarkus/github/lottery/github/GitHubService.java +++ b/src/main/java/io/quarkus/github/lottery/github/GitHubService.java @@ -8,6 +8,7 @@ import io.quarkiverse.githubapp.GitHubClientProvider; import io.quarkiverse.githubapp.GitHubConfigFileProvider; +import org.kohsuke.github.GHApp; import org.kohsuke.github.GHAppInstallation; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; @@ -23,9 +24,14 @@ public class GitHubService { public List listRepositories() throws IOException { List result = new ArrayList<>(); GitHub client = clientProvider.getApplicationClient(); - for (GHAppInstallation installation : client.getApp().listInstallations()) { - for (GHRepository repository : installation.listRepositories()) { - result.add(new GitHubRepositoryRef(installation.getId(), repository.getFullName())); + GHApp app = client.getApp(); + String appName = app.getName(); + for (GHAppInstallation installation : app.listInstallations()) { + long installationId = installation.getId(); + var installationRef = new GitHubInstallationRef(appName, installationId); + for (GHRepository repository : clientProvider.getInstallationClient(installationId) + .getInstallation().listRepositories()) { + result.add(new GitHubRepositoryRef(installationRef, repository.getFullName())); } } return result; 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 0581d1b..384d911 100644 --- a/src/main/java/io/quarkus/github/lottery/history/HistoryService.java +++ b/src/main/java/io/quarkus/github/lottery/history/HistoryService.java @@ -28,7 +28,7 @@ public LotteryHistory fetch(DrawRef drawRef, LotteryConfig config) throws IOExce var persistenceRepo = persistenceRepo(drawRef, config); var history = new LotteryHistory(drawRef.instant(), config.buckets()); String historyTopic = messageFormatter.formatHistoryTopicText(drawRef); - persistenceRepo.extractCommentsFromDedicatedIssue(persistenceRepo.selfUsername(), historyTopic, history.since()) + persistenceRepo.extractCommentsFromDedicatedIssue(persistenceRepo.selfLogin(), historyTopic, history.since()) .flatMap(uncheckedIO(message -> messageFormatter.extractPayloadFromHistoryBodyMarkdown(message).stream())) .forEach(history::add); return history; @@ -38,7 +38,7 @@ public void append(DrawRef drawRef, LotteryConfig config, List this.formatHistoryBodyReport(drawRef, report)) .collect(Collectors.joining("\n")) + "\n" + PAYLOAD_BEGIN + jsonObjectMapper.writeValueAsString(reports) + PAYLOAD_END; } - private String formatHistoryBodyReport(LotteryReport.Serialized report) { + private String formatHistoryBodyReport(DrawRef drawRef, LotteryReport.Serialized report) { StringBuilder builder = new StringBuilder("# ").append(report.username()).append('\n'); - builder.append(formatHistoryBodyBucket("Triage", report.triage())); + builder.append(formatHistoryBodyBucket(drawRef, "Triage", report.triage())); return builder.toString(); } - private String formatHistoryBodyBucket(String title, LotteryReport.Bucket.Serialized bucket) { + private String formatHistoryBodyBucket(DrawRef drawRef, String title, LotteryReport.Bucket.Serialized bucket) { + String repoName = drawRef.repositoryRef().repositoryName(); StringBuilder builder = new StringBuilder("## ").append(title).append('\n'); var issueNumbers = bucket.issueNumbers(); if (!issueNumbers.isEmpty()) { builder.append(issueNumbers.stream() - .map(issueId -> "#" + issueId) + .map(issueId -> repoName + "#" + issueId) .collect(MARKDOWN_BULLET_LIST_COLLECTOR)); } return builder.toString(); diff --git a/src/main/java/io/quarkus/github/lottery/notification/NotificationService.java b/src/main/java/io/quarkus/github/lottery/notification/NotificationService.java index 9e93ebf..72d07ea 100644 --- a/src/main/java/io/quarkus/github/lottery/notification/NotificationService.java +++ b/src/main/java/io/quarkus/github/lottery/notification/NotificationService.java @@ -15,7 +15,7 @@ public class NotificationService { public static GitHubRepository notificationRepository(GitHubService gitHubService, DrawRef drawRef, LotteryConfig.NotificationsConfig config) { - return gitHubService.repository(new GitHubRepositoryRef(drawRef.repositoryRef().installationId(), + return gitHubService.repository(new GitHubRepositoryRef(drawRef.repositoryRef().installationRef(), config.createIssues().repository())); } diff --git a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java index 88624f4..f54fcad 100644 --- a/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/GitHubServiceTest.java @@ -30,8 +30,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import io.quarkus.github.lottery.github.GitHubInstallationRef; import org.kohsuke.github.GHApp; import org.kohsuke.github.GHAppInstallation; +import org.kohsuke.github.GHAuthenticatedAppInstallation; import org.kohsuke.github.GHDirection; import org.kohsuke.github.GHIssueBuilder; import org.kohsuke.github.GHIssueQueryBuilder; @@ -59,29 +61,43 @@ @ExtendWith(MockitoExtension.class) public class GitHubServiceTest { + private final GitHubInstallationRef installationRef = new GitHubInstallationRef("quarkus-github-lottery", 1234L); + @Inject GitHubService gitHubService; @Test void listRepositories() throws IOException { - GitHubRepositoryRef repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus"); + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); given() .github(mocks -> { var applicationClient = mocks.applicationClient(); - var appMock = mocks.ghObject(GHApp.class, repoRef.installationId()); - when(applicationClient.getApp()).thenReturn(appMock); - - var installationMock = mocks.ghObject(GHAppInstallation.class, repoRef.installationId()); - var installationsMocks = mockPagedIterable(installationMock); - when(appMock.listInstallations()).thenReturn(installationsMocks); - when(installationMock.getId()).thenReturn(repoRef.installationId()); - var installationRepositoryMock = Mockito.mock(GHRepository.class); - var installationRepositoryMocks = mockPagedIterable(installationRepositoryMock); - when(installationMock.listRepositories()).thenReturn(installationRepositoryMocks); - when(installationRepositoryMock.getFullName()).thenReturn(repoRef.repositoryName()); + { + // Scope: application client + var appMock = mocks.ghObject(GHApp.class, 1); + when(applicationClient.getApp()).thenReturn(appMock); + when(appMock.getName()).thenReturn(installationRef.appName()); + + var installationMock = Mockito.mock(GHAppInstallation.class); + when(installationMock.getId()).thenReturn(installationRef.installationId()); + var installationsMocks = mockPagedIterable(installationMock); + when(appMock.listInstallations()).thenReturn(installationsMocks); + } + + var installationClient = mocks.installationClient(installationRef.installationId()); + { + // Scope: installation client + var installationMock = Mockito.mock(GHAuthenticatedAppInstallation.class); + when(installationClient.getInstallation()).thenReturn(installationMock); + + var installationRepositoryMock = Mockito.mock(GHRepository.class); + var installationRepositoryMocks = mockPagedIterable(installationRepositoryMock); + when(installationMock.listRepositories()).thenReturn(installationRepositoryMocks); + when(installationRepositoryMock.getFullName()).thenReturn(repoRef.repositoryName()); + } }) .when(() -> { assertThat(gitHubService.listRepositories()) @@ -95,12 +111,12 @@ void listRepositories() throws IOException { @Test void fetchLotteryConfig() throws IOException { - GitHubRepositoryRef repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus"); + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); given() .github(mocks -> { var repositoryMock = mocks.repository(repoRef.repositoryName()); - mocks.configFile(repositoryMock, "quarkus-github-lottery.yaml") + mocks.configFile(repositoryMock, "quarkus-github-lottery.yml") .fromString(""" notifications: createIssues: @@ -154,7 +170,7 @@ void fetchLotteryConfig() throws IOException { @Test void issuesWithLabel() throws IOException { - GitHubRepositoryRef repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus"); + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, withSettings().defaultAnswer(Answers.RETURNS_SELF)); @@ -209,7 +225,7 @@ void issuesWithLabel() throws IOException { @Test void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + 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, @@ -228,12 +244,12 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Excep .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue("quarkus-lottery-bot", + assertThat(repo.extractCommentsFromDedicatedIssue(installationRef.appName() + "[bot]", "Lottery history for quarkusio/quarkus", since)) .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).assignee("quarkus-lottery-bot"); + verify(queryIssuesBuilderMock).assignee(installationRef.appName() + "[bot]"); verifyNoMoreInteractions(queryIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); @@ -242,7 +258,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueDoesNotExist() throws Excep @Test void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExist() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + 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, @@ -258,13 +274,8 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExis var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); - var clientMock = mocks.installationClient(repoRef.installationId()); - var mySelfMock = mocks.ghObject(GHMyself.class, 1L); - when(clientMock.getMyself()).thenReturn(mySelfMock); - when(mySelfMock.getId()).thenReturn(1L); - var someoneElseMock = mocks.ghObject(GHUser.class, 2L); - when(someoneElseMock.getId()).thenReturn(2L); + when(someoneElseMock.getLogin()).thenReturn("yrodiere"); var issue2Comment1Mock = mocks.issueComment(201); when(issue2Comment1Mock.getUser()).thenReturn(someoneElseMock); @@ -276,12 +287,12 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExis .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue("quarkus-lottery-bot", + assertThat(repo.extractCommentsFromDedicatedIssue(installationRef.appName() + "[bot]", "Lottery history for quarkusio/quarkus", since)) .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).assignee("quarkus-lottery-bot"); + verify(queryIssuesBuilderMock).assignee(installationRef.appName() + "[bot]"); verifyNoMoreInteractions(queryIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); @@ -290,7 +301,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsDoNotExis @Test void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_allTooOld() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + 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, @@ -306,13 +317,10 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_all var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); - var clientMock = mocks.installationClient(repoRef.installationId()); var mySelfMock = mocks.ghObject(GHMyself.class, 1L); - when(clientMock.getMyself()).thenReturn(mySelfMock); - when(mySelfMock.getId()).thenReturn(1L); - + when(mySelfMock.getLogin()).thenReturn(installationRef.appName() + "[bot]"); var someoneElseMock = mocks.ghObject(GHUser.class, 2L); - when(someoneElseMock.getId()).thenReturn(2L); + when(someoneElseMock.getLogin()).thenReturn("yrodiere"); var issue2Comment1Mock = mocks.issueComment(201); when(issue2Comment1Mock.getUser()).thenReturn(mySelfMock); @@ -328,12 +336,12 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_all .when(() -> { var repo = gitHubService.repository(repoRef); - assertThat(repo.extractCommentsFromDedicatedIssue("quarkus-lottery-bot", + assertThat(repo.extractCommentsFromDedicatedIssue(installationRef.appName() + "[bot]", "Lottery history for quarkusio/quarkus", since)) .isEmpty(); }) .then().github(mocks -> { - verify(queryIssuesBuilderMock).assignee("quarkus-lottery-bot"); + verify(queryIssuesBuilderMock).assignee(installationRef.appName() + "[bot]"); verifyNoMoreInteractions(queryIssuesBuilderMock); verifyNoMoreInteractions(mocks.ghObjects()); @@ -342,7 +350,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist_all @Test void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + 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, @@ -358,13 +366,10 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() t var issuesMocks = mockPagedIterable(issue1Mock, issue2Mock); when(queryIssuesBuilderMock.list()).thenReturn(issuesMocks); - var clientMock = mocks.installationClient(repoRef.installationId()); var mySelfMock = mocks.ghObject(GHMyself.class, 1L); - when(clientMock.getMyself()).thenReturn(mySelfMock); - when(mySelfMock.getId()).thenReturn(1L); - + when(mySelfMock.getLogin()).thenReturn(installationRef.appName() + "[bot]"); var someoneElseMock = mocks.ghObject(GHUser.class, 2L); - when(someoneElseMock.getId()).thenReturn(2L); + when(someoneElseMock.getLogin()).thenReturn("yrodiere"); var issue2Comment1Mock = mocks.issueComment(201); when(issue2Comment1Mock.getUser()).thenReturn(mySelfMock); @@ -401,7 +406,7 @@ void extractCommentsFromDedicatedIssue_dedicatedIssueExists_appCommentsExist() t @SuppressWarnings("unchecked") @Test void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, @@ -419,13 +424,10 @@ void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { when(issue2Mock.getState()).thenReturn(GHIssueState.OPEN); - var clientMock = mocks.installationClient(repoRef.installationId()); var mySelfMock = mocks.ghObject(GHMyself.class, 1L); - when(clientMock.getMyself()).thenReturn(mySelfMock); - when(mySelfMock.getId()).thenReturn(1L); - + when(mySelfMock.getLogin()).thenReturn(installationRef.appName() + "[bot]"); var someoneElseMock = mocks.ghObject(GHUser.class, 2L); - when(someoneElseMock.getId()).thenReturn(2L); + when(someoneElseMock.getLogin()).thenReturn("yrodiere"); var issue2Comment1Mock = mocks.issueComment(201); when(issue2Comment1Mock.getUser()).thenReturn(mySelfMock); @@ -448,7 +450,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { verify(queryIssuesBuilderMock).assignee("yrodiere"); var mapCaptor = ArgumentCaptor.forClass(Map.class); - verify(mocks.installationGraphQLClient(repoRef.installationId())) + verify(mocks.installationGraphQLClient(installationRef.installationId())) .executeSync(anyString(), mapCaptor.capture()); verify(mocks.issue(2)).comment("Some content"); @@ -463,7 +465,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_open() throws Exception { @SuppressWarnings("unchecked") @Test void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + var repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus-lottery-reports"); var commentToMinimizeNodeId = "MDM6Qm90NzUwNjg0Mzg="; var queryIssuesBuilderMock = Mockito.mock(GHIssueQueryBuilder.ForRepository.class, @@ -481,13 +483,10 @@ void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { when(issue2Mock.getState()).thenReturn(GHIssueState.CLOSED); - var clientMock = mocks.installationClient(repoRef.installationId()); var mySelfMock = mocks.ghObject(GHMyself.class, 1L); - when(clientMock.getMyself()).thenReturn(mySelfMock); - when(mySelfMock.getId()).thenReturn(1L); - + when(mySelfMock.getLogin()).thenReturn(installationRef.appName() + "[bot]"); var someoneElseMock = mocks.ghObject(GHUser.class, 2L); - when(someoneElseMock.getId()).thenReturn(2L); + when(someoneElseMock.getLogin()).thenReturn("yrodiere"); var issue2Comment1Mock = mocks.issueComment(201); when(issue2Comment1Mock.getUser()).thenReturn(mySelfMock); @@ -512,7 +511,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { verify(mocks.issue(2)).reopen(); var mapCaptor = ArgumentCaptor.forClass(Map.class); - verify(mocks.installationGraphQLClient(repoRef.installationId())) + verify(mocks.installationGraphQLClient(installationRef.installationId())) .executeSync(anyString(), mapCaptor.capture()); verify(mocks.issue(2)).comment("Some content"); @@ -526,7 +525,7 @@ void commentOnDedicatedIssue_dedicatedIssueExists_closed() throws Exception { @Test void commentOnDedicatedIssue_dedicatedIssueDoesNotExist() throws IOException { - var repoRef = new GitHubRepositoryRef(1234L, "quarkusio/quarkus-lottery-reports"); + 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, diff --git a/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java b/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java index 66d69ea..68fe084 100644 --- a/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/HistoryServiceTest.java @@ -20,6 +20,7 @@ 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; @@ -42,6 +43,7 @@ public class HistoryServiceTest { MessageFormatter messageFormatterMock; + GitHubInstallationRef installationRef; GitHubRepositoryRef repoRef; Instant now; DrawRef drawRef; @@ -53,7 +55,8 @@ public class HistoryServiceTest { void setup() { gitHubServiceMock = Mockito.mock(GitHubService.class); QuarkusMock.installMockForType(gitHubServiceMock, GitHubService.class); - repoRef = new GitHubRepositoryRef(1L, "quarkusio/quarkus"); + installationRef = new GitHubInstallationRef("quarkus-github-lottery", 1L); + repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); persistenceRepoMock = Mockito.mock(GitHubRepository.class); @@ -73,14 +76,14 @@ void lastNotificationInstantForUser_noHistory() throws Exception { new LotteryConfig.BucketsConfig.TriageBucketConfig("needs-triage", Duration.ofDays(3))), List.of()); - var persistenceRepoRef = new GitHubRepositoryRef(repoRef.installationId(), + var persistenceRepoRef = new GitHubRepositoryRef(installationRef, config.notifications().createIssues().repository()); when(gitHubServiceMock.repository(persistenceRepoRef)).thenReturn(persistenceRepoMock); String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String selfUsername = "quarkus-lottery-bot"; - when(persistenceRepoMock.selfUsername()).thenReturn(selfUsername); + when(persistenceRepoMock.selfLogin()).thenReturn(selfUsername); when(persistenceRepoMock.extractCommentsFromDedicatedIssue(eq(selfUsername), eq(topic), any())) .thenAnswer(ignored -> Stream.of()); @@ -101,14 +104,14 @@ void lastNotificationInstantForUser_notNotified() throws Exception { new LotteryConfig.BucketsConfig.TriageBucketConfig("needs-triage", Duration.ofDays(3))), List.of()); - var persistenceRepoRef = new GitHubRepositoryRef(repoRef.installationId(), + var persistenceRepoRef = new GitHubRepositoryRef(installationRef, config.notifications().createIssues().repository()); when(gitHubServiceMock.repository(persistenceRepoRef)).thenReturn(persistenceRepoMock); String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String selfUsername = "quarkus-lottery-bot"; - when(persistenceRepoMock.selfUsername()).thenReturn(selfUsername); + when(persistenceRepoMock.selfLogin()).thenReturn(selfUsername); String historyBody = "Some content"; when(persistenceRepoMock.extractCommentsFromDedicatedIssue(eq(selfUsername), eq(topic), any())) .thenAnswer(ignored -> Stream.of(historyBody)); @@ -134,14 +137,14 @@ void lastNotificationInstantForUser_notifiedRecently() throws Exception { new LotteryConfig.BucketsConfig.TriageBucketConfig("needs-triage", Duration.ofDays(3))), List.of()); - var persistenceRepoRef = new GitHubRepositoryRef(repoRef.installationId(), + var persistenceRepoRef = new GitHubRepositoryRef(installationRef, config.notifications().createIssues().repository()); when(gitHubServiceMock.repository(persistenceRepoRef)).thenReturn(persistenceRepoMock); String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String selfUsername = "quarkus-lottery-bot"; - when(persistenceRepoMock.selfUsername()).thenReturn(selfUsername); + when(persistenceRepoMock.selfLogin()).thenReturn(selfUsername); String historyBody = "Some content"; when(persistenceRepoMock.extractCommentsFromDedicatedIssue(eq(selfUsername), eq(topic), any())) .thenAnswer(ignored -> Stream.of(historyBody)); @@ -169,14 +172,14 @@ void lastNotificationExpiredForIssueNumber_noHistory() throws Exception { new LotteryConfig.BucketsConfig.TriageBucketConfig("needs-triage", Duration.ofDays(3))), List.of()); - var persistenceRepoRef = new GitHubRepositoryRef(repoRef.installationId(), + var persistenceRepoRef = new GitHubRepositoryRef(installationRef, config.notifications().createIssues().repository()); when(gitHubServiceMock.repository(persistenceRepoRef)).thenReturn(persistenceRepoMock); String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String selfUsername = "quarkus-lottery-bot"; - when(persistenceRepoMock.selfUsername()).thenReturn(selfUsername); + when(persistenceRepoMock.selfLogin()).thenReturn(selfUsername); when(persistenceRepoMock.extractCommentsFromDedicatedIssue(eq(selfUsername), eq(topic), any())) .thenAnswer(ignored -> Stream.of()); @@ -197,14 +200,14 @@ void lastNotificationExpiredForIssueNumber() throws Exception { new LotteryConfig.BucketsConfig.TriageBucketConfig("needs-triage", Duration.ofDays(3))), List.of()); - var persistenceRepoRef = new GitHubRepositoryRef(repoRef.installationId(), + var persistenceRepoRef = new GitHubRepositoryRef(installationRef, config.notifications().createIssues().repository()); when(gitHubServiceMock.repository(persistenceRepoRef)).thenReturn(persistenceRepoMock); String topic = "Lottery history for quarkusio/quarkus"; when(messageFormatterMock.formatHistoryTopicText(drawRef)).thenReturn(topic); String selfUsername = "quarkus-lottery-bot"; - when(persistenceRepoMock.selfUsername()).thenReturn(selfUsername); + when(persistenceRepoMock.selfLogin()).thenReturn(selfUsername); String historyBody = "Some content"; when(persistenceRepoMock.extractCommentsFromDedicatedIssue(eq(selfUsername), eq(topic), any())) .thenAnswer(ignored -> Stream.of(historyBody)); diff --git a/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java b/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java index a502dd2..48974da 100644 --- a/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java +++ b/src/test/java/io/quarkus/github/lottery/LotterySingleRepositoryTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import io.quarkus.github.lottery.github.GitHubInstallationRef; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -57,6 +58,7 @@ public class LotterySingleRepositoryTest { NotificationService notificationServiceMock; HistoryService historyServiceMock; + GitHubInstallationRef installationRef; GitHubRepositoryRef repoRef; DrawRef drawRef; @@ -67,7 +69,8 @@ public class LotterySingleRepositoryTest { void setup() throws IOException { gitHubServiceMock = Mockito.mock(GitHubService.class); QuarkusMock.installMockForType(gitHubServiceMock, GitHubService.class); - repoRef = new GitHubRepositoryRef(1L, "quarkusio/quarkus"); + installationRef = new GitHubInstallationRef("quarkus-github-lottery", 1L); + repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); when(gitHubServiceMock.listRepositories()).thenReturn(List.of(repoRef)); repoMock = Mockito.mock(GitHubRepository.class); diff --git a/src/test/java/io/quarkus/github/lottery/MessageFormatterTest.java b/src/test/java/io/quarkus/github/lottery/MessageFormatterTest.java index a2d23a4..7f928e0 100644 --- a/src/test/java/io/quarkus/github/lottery/MessageFormatterTest.java +++ b/src/test/java/io/quarkus/github/lottery/MessageFormatterTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import io.quarkus.github.lottery.github.GitHubInstallationRef; import org.mockito.junit.jupiter.MockitoExtension; import io.quarkus.github.lottery.draw.DrawRef; @@ -28,6 +29,7 @@ @ExtendWith(MockitoExtension.class) public class MessageFormatterTest { + GitHubInstallationRef installationRef; GitHubRepositoryRef repoRef; DrawRef drawRef; @@ -36,7 +38,8 @@ public class MessageFormatterTest { @BeforeEach void setup() { - repoRef = new GitHubRepositoryRef(1L, "quarkusio/quarkus"); + installationRef = new GitHubInstallationRef("quarkus-github-lottery", 1L); + repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); var now = LocalDateTime.of(2017, 11, 6, 6, 0).toInstant(ZoneOffset.UTC); drawRef = new DrawRef(repoRef, now); } @@ -111,13 +114,13 @@ void formatHistoryBodyMarkdown() throws IOException { # yrodiere ## Triage - - #1 - - #3 + - quarkusio/quarkus#1 + - quarkusio/quarkus#3 # gsmet ## Triage - - #2 - - #4 + - quarkusio/quarkus#2 + - quarkusio/quarkus#4 # rick ## Triage diff --git a/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java b/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java index 4ec6e5d..74f78e0 100644 --- a/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java +++ b/src/test/java/io/quarkus/github/lottery/NotificationServiceTest.java @@ -16,6 +16,7 @@ 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; @@ -40,6 +41,7 @@ public class NotificationServiceTest { MessageFormatter messageFormatterMock; + GitHubInstallationRef installationRef; GitHubRepositoryRef repoRef; DrawRef drawRef; @@ -50,7 +52,8 @@ public class NotificationServiceTest { void setup() { gitHubServiceMock = Mockito.mock(GitHubService.class); QuarkusMock.installMockForType(gitHubServiceMock, GitHubService.class); - repoRef = new GitHubRepositoryRef(1L, "quarkusio/quarkus"); + installationRef = new GitHubInstallationRef("quarkus-github-lottery", 1L); + repoRef = new GitHubRepositoryRef(installationRef, "quarkusio/quarkus"); notificationRepoMock = Mockito.mock(GitHubRepository.class); @@ -65,7 +68,7 @@ void simple() throws IOException { var config = new LotteryConfig.NotificationsConfig( new LotteryConfig.NotificationsConfig.CreateIssuesConfig("quarkusio/quarkus-lottery-reports")); - var notificationRepoRef = new GitHubRepositoryRef(repoRef.installationId(), config.createIssues().repository()); + var notificationRepoRef = new GitHubRepositoryRef(installationRef, config.createIssues().repository()); when(gitHubServiceMock.repository(notificationRepoRef)).thenReturn(notificationRepoMock); Notifier notifier = notificationService.notifier(drawRef, config);