diff --git a/README.adoc b/README.adoc index 2acb427..a8bd3fb 100644 --- a/README.adoc +++ b/README.adoc @@ -31,13 +31,13 @@ triage: - labels: [area/amazon-lambda] title: "lambda" notify: [patriot1burke, matejvasek] - directories: + files: - extensions/amazon-lambda - integration-tests/amazon-lambda - labels: [area/persistence] title: "db2" notify: [aguibert] - directories: + files: - extensions/reactive-db2-client/ - extensions/jdbc/jdbc-db2/ ---- @@ -88,9 +88,9 @@ There are a few differences though as it doesn't behave in the exact same way. For pull requests, each rule can be triggered by: -* `directories` - if any file in the commits of the pull requests match, trigger the rule. This is not a regexp (it uses `startsWith`) but glob type expression are supported too `extensions/test/**`. +* `files` - if any file in the commits of the pull requests match, trigger the rule. This is not a regexp (it uses `startsWith`) but glob type expression are supported too `extensions/test/**`. -If no rule is triggered based on directories, or if rules are triggered but they all specify `allowSecondPass: true`, +If no rule is triggered based on files, or if rules are triggered but they all specify `allowSecondPass: true`, a second pass will be executed; in that second pass, rules can be triggered by: * `title` - if the title matches this regular expression (case insensitively), trigger the rule @@ -113,7 +113,7 @@ triage: title: "lambda" notify: [patriot1burke, matejvasek] notifyInPullRequest: true - directories: + files: - extensions/amazon-lambda - integration-tests/amazon-lambda ---- @@ -193,6 +193,46 @@ When a workflow run associated to a pull request is completed, a report is gener > image::documentation/screenshots/workflow-run-report.png[] +=== Approve workflow runs + +This rule applies more fine-grained protections to workflow runs +than is provided by the basic GitHub settings. If a repository +is https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository[set up to only allow workflow runs from committers], +the bot can automatically approve some workflows which meet a set of rules. + +Syntax of the `.github/quarkus-github-bot.yml` file is as follows: + +[source, yaml] +---- +features: [ APPROVE_WORKFLOWS ] +workflows: + rules: + - allow: + files: + - ./src + - ./doc* + - "**/README.md" + users: + minContributions: 5 + unless: + files: + - ./.github + - "**/pom.xml" +---- + +Workflows will be allowed if they meet one of the rules in the `allow` section, +unless one of the rules in the `unless` section is triggered. + +In the example above, any file called `README.md` would be allowed, except for `./github/README.md`. +Users who had made at least 5 commits to the repository would be allowed to make any changes, +except to a `pom.xml` or any files in `.github`. Other users could make changes to `./src` or directories whose name started with `./doc`. + +If the rule is triggered, the following actions will be executed: + +* `approve` - will approve the workflow which needs approval + +If the workflow is not approved, it will be left untouched, for a human approver to look at. + === Mark closed pull requests as invalid If a pull request is closed without being merged, we automatically add the `triage/invalid` label to the pull request. diff --git a/src/main/java/io/quarkus/bot/ApproveWorkflow.java b/src/main/java/io/quarkus/bot/ApproveWorkflow.java new file mode 100644 index 0000000..82ea978 --- /dev/null +++ b/src/main/java/io/quarkus/bot/ApproveWorkflow.java @@ -0,0 +1,215 @@ +package io.quarkus.bot; + +import io.quarkiverse.githubapp.ConfigFile; +import io.quarkiverse.githubapp.event.WorkflowRun; +import io.quarkus.bot.config.Feature; +import io.quarkus.bot.config.QuarkusGitHubBotConfig; +import io.quarkus.bot.config.QuarkusGitHubBotConfigFile; +import io.quarkus.bot.util.PullRequestFilesMatcher; +import io.quarkus.cache.CacheKey; +import io.quarkus.cache.CacheResult; +import org.jboss.logging.Logger; +import org.kohsuke.github.GHEventPayload; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepositoryStatistics; +import org.kohsuke.github.GHWorkflowRun; +import org.kohsuke.github.PagedIterable; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +class ApproveWorkflow { + + private static final Logger LOG = Logger.getLogger(ApproveWorkflow.class); + + @Inject + QuarkusGitHubBotConfig quarkusBotConfig; + + void evaluatePullRequest( + @WorkflowRun.Requested GHEventPayload.WorkflowRun workflowPayload, + @ConfigFile("quarkus-github-bot.yml") QuarkusGitHubBotConfigFile quarkusBotConfigFile) throws IOException { + if (!Feature.APPROVE_WORKFLOWS.isEnabled(quarkusBotConfigFile)) { + return; + } + + // Don't bother checking if there are no rules + if (quarkusBotConfigFile.workflows.rules != null && quarkusBotConfigFile.workflows.rules.isEmpty()) { + return; + } + GHWorkflowRun workflowRun = workflowPayload.getWorkflowRun(); + + // Only check workflows which need action + if (!GHWorkflowRun.Conclusion.ACTION_REQUIRED.equals(workflowRun.getConclusion())) { + return; + } + + ApprovalStatus approval = new ApprovalStatus(); + + checkUser(workflowPayload, quarkusBotConfigFile, approval); + + // Don't bother checking more if we have a red flag + // (but don't return because we need to do stuff with the answer) + if (!approval.hasRedFlag()) { + checkFiles(quarkusBotConfigFile, workflowRun, approval); + } + + if (approval.isApproved()) { + processApproval(workflowRun); + } + } + + private void processApproval(GHWorkflowRun workflowRun) throws IOException { + // We could also do things here like adding comments, subject to config + if (!quarkusBotConfig.isDryRun()) { + workflowRun.approve(); + } + } + + private void checkUser(GHEventPayload.WorkflowRun workflowPayload, QuarkusGitHubBotConfigFile quarkusBotConfigFile, + ApprovalStatus approval) { + for (QuarkusGitHubBotConfigFile.WorkflowApprovalRule rule : quarkusBotConfigFile.workflows.rules) { + // We allow if the files or directories match the allow rule ... + if ((rule.allow != null && rule.allow.users != null) || (rule.unless != null && rule.unless.users != null)) { + GHRepositoryStatistics.ContributorStats stats = getStatsForUser(workflowPayload); + if (matchRuleForUser(stats, rule.allow)) { + approval.shouldApprove = true; + } + + if (matchRuleForUser(stats, rule.unless)) { + approval.shouldNotApprove = true; + } + } + } + } + + private void checkFiles(QuarkusGitHubBotConfigFile quarkusBotConfigFile, GHWorkflowRun workflowRun, + ApprovalStatus approval) { + String sha = workflowRun.getHeadSha(); + + // Now we want to get the pull request we're supposed to be checking. + // It would be nice to use commit.listPullRequests() but that only returns something if the + // base and head of the PR are from the same repository, which rules out most scenarios where we would want to do an approval + + String fullyQualifiedBranchName = workflowRun.getHeadRepository().getOwnerName() + ":" + workflowRun.getHeadBranch(); + + PagedIterable pullRequestsForThisBranch = workflowRun.getRepository().queryPullRequests() + .head(fullyQualifiedBranchName) + .list(); + + // The number of PRs with matching branch name should be exactly one, but if the PR + // has been closed it sometimes disappears from the list; also, if two branch names + // start with the same string, both will turn up in the query. + for (GHPullRequest pullRequest : pullRequestsForThisBranch) { + + // Only look at PRs whose commit sha matches + if (sha.equals(pullRequest.getHead().getSha())) { + + for (QuarkusGitHubBotConfigFile.WorkflowApprovalRule rule : quarkusBotConfigFile.workflows.rules) { + // We allow if the files or directories match the allow rule ... + if (matchRuleFromChangedFiles(pullRequest, rule.allow)) { + approval.shouldApprove = true; + } + // ... unless we also match the unless rule + if (matchRuleFromChangedFiles(pullRequest, rule.unless)) { + approval.shouldNotApprove = true; + } + } + } + } + } + + public static boolean matchRuleFromChangedFiles(GHPullRequest pullRequest, + QuarkusGitHubBotConfigFile.WorkflowApprovalCondition rule) { + // for now, we only use the files but we could also use the other rules at some point + if (rule == null) { + return false; + } + + if (rule.files == null || rule.files.isEmpty()) { + return false; + } + + PullRequestFilesMatcher prMatcher = new PullRequestFilesMatcher(pullRequest); + return prMatcher.changedFilesMatch(rule.files); + } + + private boolean matchRuleForUser(GHRepositoryStatistics.ContributorStats stats, + QuarkusGitHubBotConfigFile.WorkflowApprovalCondition rule) { + if (rule == null || stats == null) { + return false; + } + + if (rule.users == null) { + return false; + } + + if (rule.users.minContributions != null && stats.getTotal() >= rule.users.minContributions) { + return true; + } + + // We can add more rules here, for example how long the user has been contributing + + return false; + } + + private GHRepositoryStatistics.ContributorStats getStatsForUser(GHEventPayload.WorkflowRun workflowPayload) { + + String login = workflowPayload.getSender().getLogin(); + if (login != null) { + return getStatsForUser(workflowPayload.getRepository(), login); + } + return null; + } + + @CacheResult(cacheName = "contributor-cache") + GHRepositoryStatistics.ContributorStats getStatsForUser(GHRepository repository, @CacheKey String login) { + try { + Map contributorStats = getContributorStats(repository); + return contributorStats.get(login); + } catch (IOException | InterruptedException | NullPointerException e) { + // We sometimes see an NPE from PagedIterator, if a fetch does not complete properly and leaves the object in an inconsistent state + // Catching these errors allows the null result for this contributor to be cached, which is ok + LOG.error("Could not get repository contributor statistics", e); + } + + return null; + } + + // We throw errors at this level to force the cache to retry and populate itself on the next request + @CacheResult(cacheName = "stats-cache") + Map getContributorStats(GHRepository repository) + throws IOException, InterruptedException { + GHRepositoryStatistics statistics = repository.getStatistics(); + if (statistics != null) { + PagedIterable contributors = statistics.getContributorStats(); + // Pull the iterable into a list object to force the traversal of the entire list, + // since then we get a fully-warmed cache on our first request + // Convert to a map for convenience of retrieval + List statsList = contributors.toList(); + return statsList.stream() + .collect( + Collectors.toMap(contributorStats -> contributorStats.getAuthor().getLogin(), Function.identity())); + } + return null; + } + + private static class ApprovalStatus { + // There are two variables here because we check a number of indicators and a number of counter-indicators + // (ie green flags and red flags) + boolean shouldApprove = false; + boolean shouldNotApprove = false; + + boolean isApproved() { + return shouldApprove && !shouldNotApprove; + } + + public boolean hasRedFlag() { + return shouldNotApprove; + } + } +} diff --git a/src/main/java/io/quarkus/bot/config/Feature.java b/src/main/java/io/quarkus/bot/config/Feature.java index ebd4c80..3ea66ee 100644 --- a/src/main/java/io/quarkus/bot/config/Feature.java +++ b/src/main/java/io/quarkus/bot/config/Feature.java @@ -10,7 +10,8 @@ public enum Feature { SET_AREA_LABEL_COLOR, TRIAGE_ISSUES_AND_PULL_REQUESTS, TRIAGE_DISCUSSIONS, - PUSH_TO_PROJECTS; + PUSH_TO_PROJECTS, + APPROVE_WORKFLOWS; public boolean isEnabled(QuarkusGitHubBotConfigFile quarkusBotConfigFile) { if (quarkusBotConfigFile == null) { diff --git a/src/main/java/io/quarkus/bot/config/QuarkusGitHubBotConfigFile.java b/src/main/java/io/quarkus/bot/config/QuarkusGitHubBotConfigFile.java index cf75f7e..28dcd8d 100644 --- a/src/main/java/io/quarkus/bot/config/QuarkusGitHubBotConfigFile.java +++ b/src/main/java/io/quarkus/bot/config/QuarkusGitHubBotConfigFile.java @@ -1,13 +1,13 @@ package io.quarkus.bot.config; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - public class QuarkusGitHubBotConfigFile { @JsonDeserialize(as = HashSet.class) @@ -21,6 +21,8 @@ public class QuarkusGitHubBotConfigFile { public ProjectsClassic projectsClassic = new ProjectsClassic(); + public Workflows workflows = new Workflows(); + public static class TriageConfig { public List rules = new ArrayList<>(); @@ -40,9 +42,16 @@ public static class TriageRule { public String expression; + /** + * @deprecated use files instead + */ @JsonDeserialize(as = TreeSet.class) + @Deprecated(forRemoval = true) public Set directories = new TreeSet<>(); + @JsonDeserialize(as = TreeSet.class) + public Set files = new TreeSet<>(); + @JsonDeserialize(as = TreeSet.class) public Set labels = new TreeSet<>(); @@ -81,6 +90,11 @@ public static class WorkflowRunAnalysisConfig { public Set workflows = new HashSet<>(); } + public static class Workflows { + + public List rules = new ArrayList<>(); + } + public static class Projects { public List rules = new ArrayList<>(); @@ -105,6 +119,25 @@ public static class ProjectTriageRule { public String status; } + public static class WorkflowApprovalRule { + + public WorkflowApprovalCondition allow; + public WorkflowApprovalCondition unless; + + } + + public static class WorkflowApprovalCondition { + @JsonDeserialize(as = TreeSet.class) + public Set files = new TreeSet<>(); + + public UserRule users; + + } + + public static class UserRule { + public Integer minContributions; + } + boolean isFeatureEnabled(Feature feature) { return features.contains(Feature.ALL) || features.contains(feature); } diff --git a/src/main/java/io/quarkus/bot/util/PullRequestFilesMatcher.java b/src/main/java/io/quarkus/bot/util/PullRequestFilesMatcher.java new file mode 100644 index 0000000..ee6f2c3 --- /dev/null +++ b/src/main/java/io/quarkus/bot/util/PullRequestFilesMatcher.java @@ -0,0 +1,57 @@ +package io.quarkus.bot.util; + +import com.hrakaroo.glob.GlobPattern; +import com.hrakaroo.glob.MatchingEngine; +import io.quarkus.cache.CacheResult; +import org.jboss.logging.Logger; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHPullRequestFileDetail; +import org.kohsuke.github.PagedIterable; + +import java.util.Collection; + +public class PullRequestFilesMatcher { + + private static final Logger LOG = Logger.getLogger(PullRequestFilesMatcher.class); + + private final GHPullRequest pullRequest; + + public PullRequestFilesMatcher(GHPullRequest pullRequest) { + this.pullRequest = pullRequest; + } + + public boolean changedFilesMatch(Collection filenamePatterns) { + if (filenamePatterns.isEmpty()) { + return false; + } + + PagedIterable prFiles = pullRequest.listFiles(); + if (prFiles != null) { + for (GHPullRequestFileDetail changedFile : prFiles) { + for (String filenamePattern : filenamePatterns) { + + if (!filenamePattern.contains("*")) { + if (changedFile.getFilename().startsWith(filenamePattern)) { + return true; + } + } else { + try { + MatchingEngine matchingEngine = compileGlob(filenamePattern); + if (matchingEngine.matches(changedFile.getFilename())) { + return true; + } + } catch (Exception e) { + LOG.error("Error evaluating glob expression: " + filenamePattern, e); + } + } + } + } + } + return false; + } + + @CacheResult(cacheName = "glob-cache") + MatchingEngine compileGlob(String filenamePattern) { + return GlobPattern.compile(filenamePattern); + } +} diff --git a/src/main/java/io/quarkus/bot/util/Triage.java b/src/main/java/io/quarkus/bot/util/Triage.java index 6f03ea9..e9612f9 100644 --- a/src/main/java/io/quarkus/bot/util/Triage.java +++ b/src/main/java/io/quarkus/bot/util/Triage.java @@ -1,19 +1,15 @@ package io.quarkus.bot.util; +import io.quarkus.bot.config.QuarkusGitHubBotConfigFile.TriageRule; +import io.quarkus.bot.el.SimpleELContext; +import org.jboss.logging.Logger; +import org.kohsuke.github.GHPullRequest; + import javax.el.ELContext; import javax.el.ELManager; import javax.el.ExpressionFactory; import javax.el.ValueExpression; -import com.hrakaroo.glob.GlobPattern; -import com.hrakaroo.glob.MatchingEngine; -import org.jboss.logging.Logger; - -import io.quarkus.bot.config.QuarkusGitHubBotConfigFile.TriageRule; -import io.quarkus.bot.el.SimpleELContext; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHPullRequestFileDetail; - public final class Triage { private static final Logger LOG = Logger.getLogger(Triage.class); @@ -82,27 +78,16 @@ public static boolean matchRuleFromDescription(String title, String body, Triage public static boolean matchRuleFromChangedFiles(GHPullRequest pullRequest, TriageRule rule) { // for now, we only use the files but we could also use the other rules at some point - if (rule.directories.isEmpty()) { + if (rule.directories.isEmpty() && rule.files.isEmpty()) { return false; } - for (GHPullRequestFileDetail changedFile : pullRequest.listFiles()) { - for (String directory : rule.directories) { - if (!directory.contains("*")) { - if (changedFile.getFilename().startsWith(directory)) { - return true; - } - } else { - try { - MatchingEngine matchingEngine = GlobPattern.compile(directory); - if (matchingEngine.matches(changedFile.getFilename())) { - return true; - } - } catch (Exception e) { - LOG.error("Error evaluating glob expression: " + directory, e); - } - } - } + PullRequestFilesMatcher prMatcher = new PullRequestFilesMatcher(pullRequest); + if (prMatcher.changedFilesMatch(rule.files)) { + return true; + } + if (prMatcher.changedFilesMatch(rule.directories)) { + return true; } return false; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 894e37f..3b0c4d7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,10 +6,15 @@ quarkus.live-reload.instrumentation=false quarkus.qute.suffixes=md quarkus.qute.content-types."md"=text/markdown +quarkus.cache.caffeine."glob-cache".maximum-size=200 + quarkus.cache.caffeine."PushToProject.getStatusFieldValue".initial-capacity=10 quarkus.cache.caffeine."PushToProject.getStatusFieldValue".maximum-size=100 quarkus.cache.caffeine."PushToProject.getStatusFieldValue".expire-after-write=2H +quarkus.cache.caffeine."contributor-cache".expire-after-write=P2D +quarkus.cache.caffeine."stats-cache".expire-after-write=P2D + quarkus.openshift.labels."app"=quarkus-bot quarkus.openshift.annotations."kubernetes.io/tls-acme"=true quarkus.openshift.env.vars.QUARKUS_GITHUB_APP_APP_ID=90234 diff --git a/src/test/java/io/quarkus/bot/it/MockHelper.java b/src/test/java/io/quarkus/bot/it/MockHelper.java index 348e64a..9fcc378 100644 --- a/src/test/java/io/quarkus/bot/it/MockHelper.java +++ b/src/test/java/io/quarkus/bot/it/MockHelper.java @@ -1,20 +1,23 @@ package io.quarkus.bot.it; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.kohsuke.github.GHPullRequestFileDetail; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.PagedIterable; +import org.kohsuke.github.PagedIterator; +import java.io.IOException; import java.util.Iterator; import java.util.List; -import org.kohsuke.github.GHPullRequestFileDetail; -import org.kohsuke.github.PagedIterable; -import org.kohsuke.github.PagedIterator; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MockHelper { public static GHPullRequestFileDetail mockGHPullRequestFileDetail(String filename) { GHPullRequestFileDetail mock = mock(GHPullRequestFileDetail.class); - when(mock.getFilename()).thenReturn(filename); + lenient().when(mock.getFilename()).thenReturn(filename); return mock; } @@ -22,14 +25,28 @@ public static GHPullRequestFileDetail mockGHPullRequestFileDetail(String filenam @SuppressWarnings("unchecked") public static PagedIterable mockPagedIterable(T... contentMocks) { PagedIterable iterableMock = mock(PagedIterable.class); - when(iterableMock.iterator()).thenAnswer(ignored -> { + try { + lenient().when(iterableMock.toList()).thenAnswer(ignored2 -> List.of(contentMocks)); + } catch (IOException e) { + // This should never happen + // That's a classic unwise comment, but it's a mock, so surely we're safe? :) + throw new RuntimeException(e); + } + lenient().when(iterableMock.iterator()).thenAnswer(ignored -> { PagedIterator iteratorMock = mock(PagedIterator.class); Iterator actualIterator = List.of(contentMocks).iterator(); when(iteratorMock.next()).thenAnswer(ignored2 -> actualIterator.next()); - when(iteratorMock.hasNext()).thenAnswer(ignored2 -> actualIterator.hasNext()); + lenient().when(iteratorMock.hasNext()).thenAnswer(ignored2 -> actualIterator.hasNext()); + return iteratorMock; }); return iterableMock; } + public static GHUser mockUser(String login) { + GHUser user = mock(GHUser.class); + when(user.getLogin()).thenReturn(login); + return user; + } + } diff --git a/src/test/java/io/quarkus/bot/it/WorkflowApprovalTest.java b/src/test/java/io/quarkus/bot/it/WorkflowApprovalTest.java new file mode 100644 index 0000000..9300777 --- /dev/null +++ b/src/test/java/io/quarkus/bot/it/WorkflowApprovalTest.java @@ -0,0 +1,455 @@ +package io.quarkus.bot.it; + +import io.quarkiverse.githubapp.testing.GitHubAppTest; +import io.quarkiverse.githubapp.testing.dsl.GitHubMockSetupContext; +import io.quarkiverse.githubapp.testing.dsl.GitHubMockVerificationContext; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHCommitPointer; +import org.kohsuke.github.GHEvent; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHPullRequestFileDetail; +import org.kohsuke.github.GHPullRequestQueryBuilder; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepositoryStatistics; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GHWorkflowRun; +import org.kohsuke.github.PagedIterable; +import org.mockito.Answers; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; + +import static io.quarkiverse.githubapp.testing.GitHubAppTesting.given; +import static io.quarkus.bot.it.MockHelper.mockUser; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +@QuarkusTest +@GitHubAppTest +@ExtendWith(MockitoExtension.class) +public class WorkflowApprovalTest { + + // We may change our user stats in individual tests, so wipe caches before each test + @CacheInvalidateAll(cacheName = "contributor-cache") + @CacheInvalidateAll(cacheName = "stats-cache") + void setupMockQueriesAndCommits(GitHubMockSetupContext mocks) { + GHRepository repoMock = mocks.repository("bot-playground"); + GHPullRequestQueryBuilder workflowRunQueryBuilderMock = mock(GHPullRequestQueryBuilder.class, + withSettings().defaultAnswer(Answers.RETURNS_SELF)); + when(repoMock.queryPullRequests()) + .thenReturn(workflowRunQueryBuilderMock); + PagedIterable iterableMock = MockHelper.mockPagedIterable(pr(mocks)); + when(workflowRunQueryBuilderMock.list()) + .thenReturn(iterableMock); + + GHCommitPointer head = mock(GHCommitPointer.class); + when(pr(mocks).getHead()).thenReturn(head); + when(head.getSha()).thenReturn("f2b91b5e80e1880f03a91fdde381bb24debf102c"); + } + + private void setupMockUsers(GitHubMockSetupContext mocks) throws InterruptedException, IOException { + GHRepository repoMock = mocks.repository("bot-playground"); + GHRepositoryStatistics stats = mock(GHRepositoryStatistics.class); + GHRepositoryStatistics.ContributorStats contributorStats = mock(GHRepositoryStatistics.ContributorStats.class); + GHUser user = mockUser("holly-test-holly"); + when(contributorStats.getAuthor()).thenReturn(user); + PagedIterable iterableStats = MockHelper.mockPagedIterable(contributorStats); + when(stats.getContributorStats()).thenReturn(iterableStats); + when(repoMock.getStatistics()).thenReturn(stats); + + } + + @Test + void changeToAnAllowedDirectoryShouldBeApproved() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - ./src + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./src/innocuous.java")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks)).listFiles(); + verifyApproved(mocks); + }); + } + + @Test + void changeToAWildcardedDirectoryShouldBeApproved() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - "*" + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./src/innocuous.java")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks)).listFiles(); + verifyApproved(mocks); + }); + } + + @Test + void changeToADirectoryWithNoRulesShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - ./src + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./github/important.yml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks)).listFiles(); + verifyNotApproved(mocks); + }); + } + + @Test + void changeToAnAllowedAndUnlessedDirectoryShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - "*" + unless: + files: + - ./github + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./github/important.yml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks), times(2)).listFiles(); + verifyNotApproved(mocks); + }); + } + + @Test + void changeToAnAllowedDirectoryWithAnIrrelevantUnlessedDirectoryShouldBeAccepted() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - "*" + unless: + files: + - ./github + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./innocuous/important.yml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks), times(2)).listFiles(); + verifyApproved(mocks); + }); + } + + @Test + void changeToAnAllowedFileShouldBeApproved() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - "**/pom.xml" + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./innocuous/something/pom.xml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks)).listFiles(); + verifyApproved(mocks); + }); + } + + @Test + void changeToAFileInAnAllowedDirectoryWithAnIrrelevantUnlessShouldBeAllowed() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - ./src + unless: + files: + - "**/bad.xml" + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./src/good.xml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks), times(2)).listFiles(); + verifyApproved(mocks); + }); + } + + @Test + void changeToAnUnlessedFileInAnAllowedDirectoryShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - ./src + unless: + files: + - "**/bad.xml" + """); + setupMockQueriesAndCommits(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./src/bad.xml")); + when(pr(mocks).listFiles()).thenReturn(paths); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verify(pr(mocks), times(2)).listFiles(); + verifyNotApproved(mocks); + }); + } + + @Test + void changeFromAnUnknownUserShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + users: + minContributions: 5 + """); + setupMockQueriesAndCommits(mocks); + setupMockUsers(mocks); + }) + .when().payloadFromClasspath("/workflow-unknown-contributor-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verifyNotApproved(mocks); + }); + } + + @Test + void changeFromANewishUserShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + users: + minContributions: 5 + """); + setupMockQueriesAndCommits(mocks); + setupMockUsers(mocks); + GHRepositoryStatistics.ContributorStats contributorStats = mocks.repository("bot-playground").getStatistics() + .getContributorStats().iterator().next(); + when(contributorStats.getTotal()).thenReturn(1); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verifyNotApproved(mocks); + }); + } + + @Test + void changeFromAnEstablishedUserShouldBeAllowed() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + users: + minContributions: 5 + """); + setupMockQueriesAndCommits(mocks); + setupMockUsers(mocks); + GHRepositoryStatistics.ContributorStats contributorStats = mocks.repository("bot-playground").getStatistics() + .getContributorStats().iterator().next(); + when(contributorStats.getTotal()).thenReturn(20); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verifyApproved(mocks); + }); + } + + @Test + void changeFromAnEstablishedUserToADangerousFileShouldBeSoftRejected() throws Exception { + given().github(mocks -> { + mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + users: + minContributions: 5 + unless: + files: + - "**/bad.xml" + """); + setupMockQueriesAndCommits(mocks); + setupMockUsers(mocks); + PagedIterable paths = MockHelper + .mockPagedIterable(MockHelper.mockGHPullRequestFileDetail("./src/bad.xml")); + GHRepositoryStatistics.ContributorStats contributorStats = mocks.repository("bot-playground").getStatistics() + .getContributorStats().iterator().next(); + when(contributorStats.getTotal()).thenReturn(20); + }) + .when().payloadFromClasspath("/workflow-approval-needed.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verifyApproved(mocks); + }); + } + + @Test + void workflowIsPreApprovedShouldDoNothing() throws Exception { + given().github(mocks -> mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS ] + workflows: + rules: + - allow: + files: + - ./src + unless: + files: + - "**/bad.xml" + """)) + .when().payloadFromClasspath("/workflow-from-committer.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + // No interactions expected, because the workflow is already in an active state + verifyNoMoreInteractions(mocks.ghObjects()); + }); + } + + @Test + void noRulesShouldDoNothing() throws Exception { + given().github(mocks -> mocks.configFileFromString( + "quarkus-github-bot.yml", + """ + features: [ APPROVE_WORKFLOWS, ALL ] + workflows: + rules: + """)) + .when().payloadFromClasspath("/workflow-from-committer.json") + .event(GHEvent.WORKFLOW_RUN) + .then().github(mocks -> { + verifyNoMoreInteractions(mocks.ghObjects()); + }); + } + + private void verifyApproved(GitHubMockVerificationContext mocks) throws Exception { + GHWorkflowRun workflow = mocks.ghObject(GHWorkflowRun.class, 2860832197l); + verify(workflow).approve(); + + } + + private void verifyNotApproved(GitHubMockVerificationContext mocks) throws Exception { + GHWorkflowRun workflow = mocks.ghObject(GHWorkflowRun.class, 2860832197l); + verify(workflow, never()).approve(); + + } + + private GHPullRequest pr(GitHubMockSetupContext mocks) { + return mocks.pullRequest(527350930); + } + + private GHPullRequest pr(GitHubMockVerificationContext mocks) { + return mocks.pullRequest(527350930); + } + +} diff --git a/src/test/resources/workflow-approval-needed.json b/src/test/resources/workflow-approval-needed.json new file mode 100644 index 0000000..522f977 --- /dev/null +++ b/src/test/resources/workflow-approval-needed.json @@ -0,0 +1,357 @@ +{ + "action": "requested", + "workflow_run": { + "id": 2860832197, + "name": "CI", + "node_id": "WFR_kwLOHzCew86qhNXF", + "head_branch": "main", + "head_sha": "f2b91b5e80e1880f03a91fdde381bb24debf102c", + "path": ".github/workflows/blank.yml", + "run_number": 15, + "event": "pull_request", + "status": "completed", + "conclusion": "action_required", + "workflow_id": 32423768, + "check_suite_id": 7817139885, + "check_suite_node_id": "CS_kwDOHzCew88AAAAB0fAWrQ", + "url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197", + "html_url": "https://github.com/holly-cummins/bot-playground/actions/runs/2860832197", + "pull_requests": [], + "created_at": "2022-08-15T13:08:52Z", + "updated_at": "2022-08-15T13:08:52Z", + "actor": { + "login": "holly-test-holly", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-test-holly", + "html_url": "https://github.com/holly-test-holly", + "followers_url": "https://api.github.com/users/holly-test-holly/followers", + "following_url": "https://api.github.com/users/holly-test-holly/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-test-holly/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-test-holly/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-test-holly/subscriptions", + "organizations_url": "https://api.github.com/users/holly-test-holly/orgs", + "repos_url": "https://api.github.com/users/holly-test-holly/repos", + "events_url": "https://api.github.com/users/holly-test-holly/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-test-holly/received_events", + "type": "User", + "site_admin": false + }, + "run_attempt": 1, + "referenced_workflows": [], + "run_started_at": "2022-08-15T13:08:52Z", + "triggering_actor": { + "login": "holly-test-holly", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-test-holly", + "html_url": "https://github.com/holly-test-holly", + "followers_url": "https://api.github.com/users/holly-test-holly/followers", + "following_url": "https://api.github.com/users/holly-test-holly/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-test-holly/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-test-holly/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-test-holly/subscriptions", + "organizations_url": "https://api.github.com/users/holly-test-holly/orgs", + "repos_url": "https://api.github.com/users/holly-test-holly/repos", + "events_url": "https://api.github.com/users/holly-test-holly/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-test-holly/received_events", + "type": "User", + "site_admin": false + }, + "jobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197/jobs", + "logs_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197/logs", + "check_suite_url": "https://api.github.com/repos/holly-cummins/bot-playground/check-suites/7817139885", + "artifacts_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197/artifacts", + "cancel_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197/cancel", + "rerun_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860832197/rerun", + "previous_attempt_url": null, + "workflow_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/workflows/32423768", + "head_commit": { + "id": "f2b91b5e80e1880f03a91fdde381bb24debf102c", + "tree_id": "9de3ce570c143c11b5d3b6ad38ea02b95fd9437b", + "message": "Merge branch 'holly-cummins:main' into main", + "timestamp": "2022-08-15T12:42:33Z", + "author": { + "name": "holly-test-holly", + "email": "111282252+holly-test-holly@users.noreply.github.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "holly-cummins/bot-playground", + "private": false, + "owner": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-cummins/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "forks_url": "https://api.github.com/repos/holly-cummins/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-cummins/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-cummins/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-cummins/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-cummins/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-cummins/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-cummins/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-cummins/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-cummins/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-cummins/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-cummins/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-cummins/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-cummins/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-cummins/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-cummins/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-cummins/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-cummins/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-cummins/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-cummins/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-cummins/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-cummins/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-cummins/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-cummins/bot-playground/deployments" + }, + "head_repository": { + "id": 524953530, + "node_id": "R_kgDOH0onug", + "name": "bot-playground", + "full_name": "holly-test-holly/bot-playground", + "private": false, + "owner": { + "login": "holly-test-holly", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-test-holly", + "html_url": "https://github.com/holly-test-holly", + "followers_url": "https://api.github.com/users/holly-test-holly/followers", + "following_url": "https://api.github.com/users/holly-test-holly/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-test-holly/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-test-holly/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-test-holly/subscriptions", + "organizations_url": "https://api.github.com/users/holly-test-holly/orgs", + "repos_url": "https://api.github.com/users/holly-test-holly/repos", + "events_url": "https://api.github.com/users/holly-test-holly/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-test-holly/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-test-holly/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": true, + "url": "https://api.github.com/repos/holly-test-holly/bot-playground", + "forks_url": "https://api.github.com/repos/holly-test-holly/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-test-holly/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-test-holly/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-test-holly/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-test-holly/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-test-holly/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-test-holly/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-test-holly/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-test-holly/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-test-holly/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-test-holly/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-test-holly/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-test-holly/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-test-holly/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-test-holly/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-test-holly/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-test-holly/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-test-holly/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-test-holly/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-test-holly/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-test-holly/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-test-holly/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-test-holly/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-test-holly/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-test-holly/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-test-holly/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-test-holly/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-test-holly/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-test-holly/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-test-holly/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-test-holly/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-test-holly/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-test-holly/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-test-holly/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-test-holly/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-test-holly/bot-playground/deployments" + } + }, + "workflow": { + "id": 32423768, + "node_id": "W_kwDOHzCew84B7r9Y", + "name": "CI", + "path": ".github/workflows/blank.yml", + "state": "active", + "created_at": "2022-08-15T11:10:23.000Z", + "updated_at": "2022-08-15T11:10:44.000Z", + "url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/workflows/32423768", + "html_url": "https://github.com/holly-cummins/bot-playground/blob/main/.github/workflows/blank.yml", + "badge_url": "https://github.com/holly-cummins/bot-playground/workflows/CI/badge.svg" + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "holly-cummins/bot-playground", + "private": false, + "owner": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-cummins/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "forks_url": "https://api.github.com/repos/holly-cummins/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-cummins/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-cummins/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-cummins/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-cummins/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-cummins/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-cummins/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-cummins/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-cummins/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-cummins/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-cummins/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-cummins/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-cummins/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-cummins/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-cummins/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-cummins/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-cummins/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-cummins/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-cummins/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-cummins/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-cummins/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-cummins/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-cummins/bot-playground/deployments", + "created_at": "2022-08-10T09:27:22Z", + "updated_at": "2022-08-10T09:27:22Z", + "pushed_at": "2022-08-15T13:08:50Z", + "git_url": "git://github.com/holly-cummins/bot-playground.git", + "ssh_url": "git@github.com:holly-cummins/bot-playground.git", + "clone_url": "https://github.com/holly-cummins/bot-playground.git", + "svn_url": "https://github.com/holly-cummins/bot-playground", + "homepage": null, + "size": 5, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 1, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "holly-test-holly", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-test-holly", + "html_url": "https://github.com/holly-test-holly", + "followers_url": "https://api.github.com/users/holly-test-holly/followers", + "following_url": "https://api.github.com/users/holly-test-holly/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-test-holly/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-test-holly/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-test-holly/subscriptions", + "organizations_url": "https://api.github.com/users/holly-test-holly/orgs", + "repos_url": "https://api.github.com/users/holly-test-holly/repos", + "events_url": "https://api.github.com/users/holly-test-holly/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-test-holly/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 28125889, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMjgxMjU4ODk=" + } +} \ No newline at end of file diff --git a/src/test/resources/workflow-from-committer.json b/src/test/resources/workflow-from-committer.json new file mode 100644 index 0000000..04752d4 --- /dev/null +++ b/src/test/resources/workflow-from-committer.json @@ -0,0 +1,381 @@ +{ + "action": "requested", + "workflow_run": { + "id": 2860920997, + "name": "CI", + "node_id": "WFR_kwLOHzCew86qhjCl", + "head_branch": "holly-cummins-patch-2", + "head_sha": "aae2daff80aa1207f26a4a068235bb7f55596f93", + "path": ".github/workflows/blank.yml", + "run_number": 16, + "event": "pull_request", + "status": "queued", + "conclusion": null, + "workflow_id": 32423768, + "check_suite_id": 7817373106, + "check_suite_node_id": "CS_kwDOHzCew88AAAAB0fOlsg", + "url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997", + "html_url": "https://github.com/holly-cummins/bot-playground/actions/runs/2860920997", + "pull_requests": [ + { + "url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls/12", + "id": 1026461109, + "number": 12, + "head": { + "ref": "holly-cummins-patch-2", + "sha": "aae2daff80aa1207f26a4a068235bb7f55596f93", + "repo": { + "id": 523280067, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "name": "bot-playground" + } + }, + "base": { + "ref": "main", + "sha": "011ba01fe097f0f74fd505a83db26a20432c6c06", + "repo": { + "id": 523280067, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "name": "bot-playground" + } + } + } + ], + "created_at": "2022-08-15T13:24:37Z", + "updated_at": "2022-08-15T13:24:37Z", + "actor": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "run_attempt": 1, + "referenced_workflows": [], + "run_started_at": "2022-08-15T13:24:37Z", + "triggering_actor": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "jobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997/jobs", + "logs_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997/logs", + "check_suite_url": "https://api.github.com/repos/holly-cummins/bot-playground/check-suites/7817373106", + "artifacts_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997/artifacts", + "cancel_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997/cancel", + "rerun_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/runs/2860920997/rerun", + "previous_attempt_url": null, + "workflow_url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/workflows/32423768", + "head_commit": { + "id": "aae2daff80aa1207f26a4a068235bb7f55596f93", + "tree_id": "484ba2e9f6602aa5df34bc1fec1f2250e14bdb73", + "message": "Update README.md", + "timestamp": "2022-08-15T13:23:53Z", + "author": { + "name": "Holly Cummins", + "email": "hcummins@redhat.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "holly-cummins/bot-playground", + "private": false, + "owner": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-cummins/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "forks_url": "https://api.github.com/repos/holly-cummins/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-cummins/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-cummins/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-cummins/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-cummins/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-cummins/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-cummins/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-cummins/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-cummins/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-cummins/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-cummins/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-cummins/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-cummins/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-cummins/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-cummins/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-cummins/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-cummins/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-cummins/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-cummins/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-cummins/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-cummins/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-cummins/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-cummins/bot-playground/deployments" + }, + "head_repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "holly-cummins/bot-playground", + "private": false, + "owner": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-cummins/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "forks_url": "https://api.github.com/repos/holly-cummins/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-cummins/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-cummins/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-cummins/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-cummins/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-cummins/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-cummins/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-cummins/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-cummins/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-cummins/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-cummins/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-cummins/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-cummins/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-cummins/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-cummins/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-cummins/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-cummins/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-cummins/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-cummins/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-cummins/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-cummins/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-cummins/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-cummins/bot-playground/deployments" + } + }, + "workflow": { + "id": 32423768, + "node_id": "W_kwDOHzCew84B7r9Y", + "name": "CI", + "path": ".github/workflows/blank.yml", + "state": "active", + "created_at": "2022-08-15T11:10:23.000Z", + "updated_at": "2022-08-15T11:10:44.000Z", + "url": "https://api.github.com/repos/holly-cummins/bot-playground/actions/workflows/32423768", + "html_url": "https://github.com/holly-cummins/bot-playground/blob/main/.github/workflows/blank.yml", + "badge_url": "https://github.com/holly-cummins/bot-playground/workflows/CI/badge.svg" + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "holly-cummins/bot-playground", + "private": false, + "owner": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/holly-cummins/bot-playground", + "description": "A playground repository used for testing https://github.com/holly-cummins/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/holly-cummins/bot-playground", + "forks_url": "https://api.github.com/repos/holly-cummins/bot-playground/forks", + "keys_url": "https://api.github.com/repos/holly-cummins/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/holly-cummins/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/holly-cummins/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/holly-cummins/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/holly-cummins/bot-playground/events", + "assignees_url": "https://api.github.com/repos/holly-cummins/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/holly-cummins/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/holly-cummins/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/holly-cummins/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/holly-cummins/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/holly-cummins/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/holly-cummins/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/holly-cummins/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/holly-cummins/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/holly-cummins/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/holly-cummins/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/holly-cummins/bot-playground/merges", + "archive_url": "https://api.github.com/repos/holly-cummins/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/holly-cummins/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/holly-cummins/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/holly-cummins/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/holly-cummins/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/holly-cummins/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/holly-cummins/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/holly-cummins/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/holly-cummins/bot-playground/deployments", + "created_at": "2022-08-10T09:27:22Z", + "updated_at": "2022-08-10T09:27:22Z", + "pushed_at": "2022-08-15T13:24:35Z", + "git_url": "git://github.com/holly-cummins/bot-playground.git", + "ssh_url": "git@github.com:holly-cummins/bot-playground.git", + "clone_url": "https://github.com/holly-cummins/bot-playground.git", + "svn_url": "https://github.com/holly-cummins/bot-playground", + "homepage": null, + "size": 5, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "holly-cummins", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/holly-cummins", + "html_url": "https://github.com/holly-cummins", + "followers_url": "https://api.github.com/users/holly-cummins/followers", + "following_url": "https://api.github.com/users/holly-cummins/following{/other_user}", + "gists_url": "https://api.github.com/users/holly-cummins/gists{/gist_id}", + "starred_url": "https://api.github.com/users/holly-cummins/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/holly-cummins/subscriptions", + "organizations_url": "https://api.github.com/users/holly-cummins/orgs", + "repos_url": "https://api.github.com/users/holly-cummins/repos", + "events_url": "https://api.github.com/users/holly-cummins/events{/privacy}", + "received_events_url": "https://api.github.com/users/holly-cummins/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 28125889, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMjgxMjU4ODk=" + } +} \ No newline at end of file diff --git a/src/test/resources/workflow-unknown-contributor-approval-needed.json b/src/test/resources/workflow-unknown-contributor-approval-needed.json new file mode 100644 index 0000000..256d4df --- /dev/null +++ b/src/test/resources/workflow-unknown-contributor-approval-needed.json @@ -0,0 +1,357 @@ +{ + "action": "requested", + "workflow_run": { + "id": 2860832197, + "name": "CI", + "node_id": "WFR_kwLOHzCew86qhNXF", + "head_branch": "main", + "head_sha": "f2b91b5e80e1880f03a91fdde381bb24debf102c", + "path": ".github/workflows/blank.yml", + "run_number": 15, + "event": "pull_request", + "status": "completed", + "conclusion": "action_required", + "workflow_id": 32423768, + "check_suite_id": 7817139885, + "check_suite_node_id": "CS_kwDOHzCew88AAAAB0fAWrQ", + "url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197", + "html_url": "https://github.com/the-anonymous-one/bot-playground/actions/runs/2860832197", + "pull_requests": [], + "created_at": "2022-08-15T13:08:52Z", + "updated_at": "2022-08-15T13:08:52Z", + "actor": { + "login": "doctor-anonymous", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/doctor-anonymous", + "html_url": "https://github.com/doctor-anonymous", + "followers_url": "https://api.github.com/users/doctor-anonymous/followers", + "following_url": "https://api.github.com/users/doctor-anonymous/following{/other_user}", + "gists_url": "https://api.github.com/users/doctor-anonymous/gists{/gist_id}", + "starred_url": "https://api.github.com/users/doctor-anonymous/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/doctor-anonymous/subscriptions", + "organizations_url": "https://api.github.com/users/doctor-anonymous/orgs", + "repos_url": "https://api.github.com/users/doctor-anonymous/repos", + "events_url": "https://api.github.com/users/doctor-anonymous/events{/privacy}", + "received_events_url": "https://api.github.com/users/doctor-anonymous/received_events", + "type": "User", + "site_admin": false + }, + "run_attempt": 1, + "referenced_workflows": [], + "run_started_at": "2022-08-15T13:08:52Z", + "triggering_actor": { + "login": "doctor-anonymous", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/doctor-anonymous", + "html_url": "https://github.com/doctor-anonymous", + "followers_url": "https://api.github.com/users/doctor-anonymous/followers", + "following_url": "https://api.github.com/users/doctor-anonymous/following{/other_user}", + "gists_url": "https://api.github.com/users/doctor-anonymous/gists{/gist_id}", + "starred_url": "https://api.github.com/users/doctor-anonymous/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/doctor-anonymous/subscriptions", + "organizations_url": "https://api.github.com/users/doctor-anonymous/orgs", + "repos_url": "https://api.github.com/users/doctor-anonymous/repos", + "events_url": "https://api.github.com/users/doctor-anonymous/events{/privacy}", + "received_events_url": "https://api.github.com/users/doctor-anonymous/received_events", + "type": "User", + "site_admin": false + }, + "jobs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197/jobs", + "logs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197/logs", + "check_suite_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/check-suites/7817139885", + "artifacts_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197/artifacts", + "cancel_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197/cancel", + "rerun_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/runs/2860832197/rerun", + "previous_attempt_url": null, + "workflow_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/workflows/32423768", + "head_commit": { + "id": "f2b91b5e80e1880f03a91fdde381bb24debf102c", + "tree_id": "9de3ce570c143c11b5d3b6ad38ea02b95fd9437b", + "message": "Merge branch 'the-anonymous-one:main' into main", + "timestamp": "2022-08-15T12:42:33Z", + "author": { + "name": "doctor-anonymous", + "email": "111282252+doctor-anonymous@users.noreply.github.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "the-anonymous-one/bot-playground", + "private": false, + "owner": { + "login": "the-anonymous-one", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/the-anonymous-one", + "html_url": "https://github.com/the-anonymous-one", + "followers_url": "https://api.github.com/users/the-anonymous-one/followers", + "following_url": "https://api.github.com/users/the-anonymous-one/following{/other_user}", + "gists_url": "https://api.github.com/users/the-anonymous-one/gists{/gist_id}", + "starred_url": "https://api.github.com/users/the-anonymous-one/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/the-anonymous-one/subscriptions", + "organizations_url": "https://api.github.com/users/the-anonymous-one/orgs", + "repos_url": "https://api.github.com/users/the-anonymous-one/repos", + "events_url": "https://api.github.com/users/the-anonymous-one/events{/privacy}", + "received_events_url": "https://api.github.com/users/the-anonymous-one/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/the-anonymous-one/bot-playground", + "description": "A playground repository used for testing https://github.com/the-anonymous-one/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/the-anonymous-one/bot-playground", + "forks_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/forks", + "keys_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/events", + "assignees_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/merges", + "archive_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/deployments" + }, + "head_repository": { + "id": 524953530, + "node_id": "R_kgDOH0onug", + "name": "bot-playground", + "full_name": "doctor-anonymous/bot-playground", + "private": false, + "owner": { + "login": "doctor-anonymous", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/doctor-anonymous", + "html_url": "https://github.com/doctor-anonymous", + "followers_url": "https://api.github.com/users/doctor-anonymous/followers", + "following_url": "https://api.github.com/users/doctor-anonymous/following{/other_user}", + "gists_url": "https://api.github.com/users/doctor-anonymous/gists{/gist_id}", + "starred_url": "https://api.github.com/users/doctor-anonymous/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/doctor-anonymous/subscriptions", + "organizations_url": "https://api.github.com/users/doctor-anonymous/orgs", + "repos_url": "https://api.github.com/users/doctor-anonymous/repos", + "events_url": "https://api.github.com/users/doctor-anonymous/events{/privacy}", + "received_events_url": "https://api.github.com/users/doctor-anonymous/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/doctor-anonymous/bot-playground", + "description": "A playground repository used for testing https://github.com/the-anonymous-one/quarkus-github-bot", + "fork": true, + "url": "https://api.github.com/repos/doctor-anonymous/bot-playground", + "forks_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/forks", + "keys_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/events", + "assignees_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/merges", + "archive_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/doctor-anonymous/bot-playground/deployments" + } + }, + "workflow": { + "id": 32423768, + "node_id": "W_kwDOHzCew84B7r9Y", + "name": "CI", + "path": ".github/workflows/blank.yml", + "state": "active", + "created_at": "2022-08-15T11:10:23.000Z", + "updated_at": "2022-08-15T11:10:44.000Z", + "url": "https://api.github.com/repos/the-anonymous-one/bot-playground/actions/workflows/32423768", + "html_url": "https://github.com/the-anonymous-one/bot-playground/blob/main/.github/workflows/blank.yml", + "badge_url": "https://github.com/the-anonymous-one/bot-playground/workflows/CI/badge.svg" + }, + "repository": { + "id": 523280067, + "node_id": "R_kgDOHzCeww", + "name": "bot-playground", + "full_name": "the-anonymous-one/bot-playground", + "private": false, + "owner": { + "login": "the-anonymous-one", + "id": 11509290, + "node_id": "MDQ6VXNlcjExNTA5Mjkw", + "avatar_url": "https://avatars.githubusercontent.com/u/11509290?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/the-anonymous-one", + "html_url": "https://github.com/the-anonymous-one", + "followers_url": "https://api.github.com/users/the-anonymous-one/followers", + "following_url": "https://api.github.com/users/the-anonymous-one/following{/other_user}", + "gists_url": "https://api.github.com/users/the-anonymous-one/gists{/gist_id}", + "starred_url": "https://api.github.com/users/the-anonymous-one/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/the-anonymous-one/subscriptions", + "organizations_url": "https://api.github.com/users/the-anonymous-one/orgs", + "repos_url": "https://api.github.com/users/the-anonymous-one/repos", + "events_url": "https://api.github.com/users/the-anonymous-one/events{/privacy}", + "received_events_url": "https://api.github.com/users/the-anonymous-one/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/the-anonymous-one/bot-playground", + "description": "A playground repository used for testing https://github.com/the-anonymous-one/quarkus-github-bot", + "fork": false, + "url": "https://api.github.com/repos/the-anonymous-one/bot-playground", + "forks_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/forks", + "keys_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/teams", + "hooks_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/hooks", + "issue_events_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues/events{/number}", + "events_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/events", + "assignees_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/assignees{/user}", + "branches_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/branches{/branch}", + "tags_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/tags", + "blobs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/statuses/{sha}", + "languages_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/languages", + "stargazers_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/stargazers", + "contributors_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/contributors", + "subscribers_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/subscribers", + "subscription_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/subscription", + "commits_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/contents/{+path}", + "compare_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/merges", + "archive_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/downloads", + "issues_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/issues{/number}", + "pulls_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/pulls{/number}", + "milestones_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/milestones{/number}", + "notifications_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/labels{/name}", + "releases_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/releases{/id}", + "deployments_url": "https://api.github.com/repos/the-anonymous-one/bot-playground/deployments", + "created_at": "2022-08-10T09:27:22Z", + "updated_at": "2022-08-10T09:27:22Z", + "pushed_at": "2022-08-15T13:08:50Z", + "git_url": "git://github.com/the-anonymous-one/bot-playground.git", + "ssh_url": "git@github.com:the-anonymous-one/bot-playground.git", + "clone_url": "https://github.com/the-anonymous-one/bot-playground.git", + "svn_url": "https://github.com/the-anonymous-one/bot-playground", + "homepage": null, + "size": 5, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 1, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "doctor-anonymous", + "id": 111282252, + "node_id": "U_kgDOBqIITA", + "avatar_url": "https://avatars.githubusercontent.com/u/111282252?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/doctor-anonymous", + "html_url": "https://github.com/doctor-anonymous", + "followers_url": "https://api.github.com/users/doctor-anonymous/followers", + "following_url": "https://api.github.com/users/doctor-anonymous/following{/other_user}", + "gists_url": "https://api.github.com/users/doctor-anonymous/gists{/gist_id}", + "starred_url": "https://api.github.com/users/doctor-anonymous/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/doctor-anonymous/subscriptions", + "organizations_url": "https://api.github.com/users/doctor-anonymous/orgs", + "repos_url": "https://api.github.com/users/doctor-anonymous/repos", + "events_url": "https://api.github.com/users/doctor-anonymous/events{/privacy}", + "received_events_url": "https://api.github.com/users/doctor-anonymous/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 28125889, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMjgxMjU4ODk=" + } +} \ No newline at end of file