Compare commits

...

69 Commits

Author SHA1 Message Date
Liam Newman
2844542efa [maven-release-plugin] prepare release github-api-1.115 2020-07-16 15:16:47 -07:00
Liam Newman
e3fcae9392 Revert "Remove Java 15-ea"
This reverts commit c6ccfa91f3.
2020-07-15 15:32:46 -07:00
Liam Newman
c6ccfa91f3 Remove Java 15-ea
It is unclear exactly why this started failing but we're not interested figuring this out.
2020-07-15 15:19:06 -07:00
Liam Newman
b6fcee1cb9 Rollback to surefire 2.22.2 2020-07-15 15:14:16 -07:00
Liam Newman
9071befb04 Run slow or flaky tests in test phase 2020-07-15 15:05:26 -07:00
Liam Newman
bdd5fe98f3 Use file to specify slow-or-flaky-tests list 2020-07-15 15:00:03 -07:00
Liam Newman
a3d3e83a49 Move slow or flaky tests to separate surefire execution
This lets us have most tests run immediately and with no retries and then have slower tests that may be flaky run later with retries.
2020-07-15 14:45:32 -07:00
Liam Newman
08bde72028 Merge pull request #849 from alexanderkjall/add-support-for-child-teams
Added support for fetching what teams are part of this team.
2020-07-15 14:05:47 -07:00
Liam Newman
108a136368 Remove getChildTeams and add test for no children 2020-07-15 13:46:11 -07:00
Liam Newman
57d87ad6b1 Merge branch 'master' into add-support-for-child-teams 2020-07-15 13:28:32 -07:00
Liam Newman
0c22815ff7 Merge pull request #872 from MarcosCela/doc/org-level-resources
Add documentation for organization-level resources
2020-07-15 13:27:05 -07:00
Liam Newman
0ca792ecfd Merge pull request #830 from bitwiseman/task/rate-limit/full
Handle header and endpoint rate limit responses consistently
2020-07-15 12:42:11 -07:00
Liam Newman
987c34c69e Merge branch 'master' into task/rate-limit/full 2020-07-15 12:13:16 -07:00
Liam Newman
c1c02bc8ab Merge pull request #887 from hub4j/dependabot/maven/org.mockito-mockito-core-3.4.0
Chore(deps-dev): Bump mockito-core from 3.3.3 to 3.4.0
2020-07-14 12:02:54 -07:00
dependabot[bot]
4ee369f27c Chore(deps-dev): Bump mockito-core from 3.3.3 to 3.4.0
Bumps [mockito-core](https://github.com/mockito/mockito) from 3.3.3 to 3.4.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.3.3...v3.4.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-13 15:52:33 +00:00
Liam Newman
c9012efdcb Merge pull request #886 from hub4j/dependabot/maven/net.revelc.code.formatter-formatter-maven-plugin-2.12.1
Chore(deps): Bump formatter-maven-plugin from 2.12.0 to 2.12.1
2020-07-13 08:51:51 -07:00
dependabot[bot]
41524fc67d Chore(deps): Bump formatter-maven-plugin from 2.12.0 to 2.12.1
Bumps [formatter-maven-plugin](https://github.com/revelc/formatter-maven-plugin) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/revelc/formatter-maven-plugin/releases)
- [Changelog](https://github.com/revelc/formatter-maven-plugin/blob/formatter-maven-plugin-2.12.1/CHANGELOG.md)
- [Commits](https://github.com/revelc/formatter-maven-plugin/compare/formatter-maven-plugin-2.12.0...formatter-maven-plugin-2.12.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-13 02:00:47 +00:00
Liam Newman
04ff61e981 Merge pull request #885 from XiongKezhi/add-missing-conclusion
Add the missing SKIPPED check run conclusion
2020-07-12 14:11:56 -07:00
Liam Newman
532468dc67 Update src/main/java/org/kohsuke/github/GHCheckRun.java 2020-07-12 13:41:57 -07:00
Kezhi Xiong
9c9a2dae47 Add JavaDoc for check run conclusion 2020-07-12 15:58:34 +08:00
Kezhi Xiong
c8a868b57f Add the missing SKIPPED check run conclusion 2020-07-12 15:54:09 +08:00
Liam Newman
4b3f81ee34 Clean up comments and javadoc 2020-07-10 10:51:20 -07:00
Liam Newman
afa170ba7c Add more tests for rate limit record selection 2020-07-10 09:01:29 -07:00
Liam Newman
46e3b2272e Clean up and reorganize changes
Changed GitHubRateLimitSpecifier to RateLimitTarget
Made RateLimitTarget public so it can be passed to GitHubBuilder
2020-07-10 09:01:29 -07:00
Liam Newman
52472e90ec Simplified rate limit record selection
Here we have another example of trying to do something clever when simplicity is the better choice.
Rather than trying to guess the rate limit record for a request based on the url path,
I added an enumeration which can be set on the request to say which rate limit record to applies.

This is simpler, safer, and faster than trying to guess the rate limit from the url path.
2020-07-10 09:01:29 -07:00
Liam Newman
4ef0d00846 Integrate full rate limit checking 2020-07-10 09:01:29 -07:00
Liam Newman
580f2537f2 Move GHRef readers to GHRef class 2020-07-09 15:00:40 -07:00
Liam Newman
3d9fd96026 Merge pull request #884 from bitwiseman/task/gitbucket
Workaround for GitBucket refs issue
2020-07-09 13:38:55 -07:00
Liam Newman
f449b92721 Workaround for GitBucket refs issue
The bug that caused this has been fixed by GitBucket but may not be released for some time.
It was a change to this library that broke them, so to be nice we're fully working around it here.

That said, the GET /refs endpoint is deprecated and will be going away at some point.
We will be making a breaking change in this area again in the next few months. We'll need to raise
it to GitBucket to make sure they handle it proproperly.
2020-07-09 09:50:29 -07:00
Liam Newman
3b0216b023 Merge pull request #877 from hub4j/dependabot/maven/com.github.tomakehurst-wiremock-jre8-standalone-2.27.1
Chore(deps-dev): Bump wiremock-jre8-standalone from 2.26.3 to 2.27.1
2020-07-07 12:40:23 -07:00
Liam Newman
98cf839737 Add new methods 2020-07-07 12:29:59 -07:00
dependabot[bot]
0bb0846505 Chore(deps-dev): Bump wiremock-jre8-standalone from 2.26.3 to 2.27.1
Bumps [wiremock-jre8-standalone](https://github.com/tomakehurst/wiremock) from 2.26.3 to 2.27.1.
- [Release notes](https://github.com/tomakehurst/wiremock/releases)
- [Commits](https://github.com/tomakehurst/wiremock/compare/2.26.3...2.27.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-07 19:23:07 +00:00
Liam Newman
70969400a3 Merge pull request #876 from jtnord/symlink-support
Add minimal symlink support via GHContent.getTarget()
2020-07-07 12:21:39 -07:00
Liam Newman
147e8d5d12 Merge branch 'master' into symlink-support 2020-07-07 11:38:14 -07:00
Liam Newman
cacc3e6edd Update formatter cache directory 2020-07-07 10:15:00 -07:00
Liam Newman
a284eca147 Shorten file paths for windows 2020-07-07 09:48:31 -07:00
James Nord
0d3ba9d7f0 formatting fixes 2020-07-07 13:29:33 +01:00
Liam Newman
be8064d642 Merge pull request #879 from hub4j/dependabot/maven/org.codehaus.mojo-animal-sniffer-maven-plugin-1.19
Chore(deps): Bump animal-sniffer-maven-plugin from 1.18 to 1.19
2020-07-06 10:15:33 -07:00
dependabot[bot]
e30dba742d Chore(deps): Bump animal-sniffer-maven-plugin from 1.18 to 1.19
Bumps [animal-sniffer-maven-plugin](https://github.com/mojohaus/animal-sniffer) from 1.18 to 1.19.
- [Release notes](https://github.com/mojohaus/animal-sniffer/releases)
- [Commits](https://github.com/mojohaus/animal-sniffer/compare/animal-sniffer-parent-1.18...animal-sniffer-parent-1.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-06 02:01:00 +00:00
James Nord
44b72ed647 Add initial symlink support.
this adds rudimentary symlink support (just enough to get a symlink).

still todo, howto handle GHRepository.getFileContent where the path
contains a symlink within the repository
2020-07-02 17:18:20 +01:00
Liam Newman
666bd77dac Merge pull request #874 from hub4j/bitwiseman-patch-1
Add "workflow_dispatch" to GHEvent
2020-07-02 08:52:31 -07:00
Liam Newman
0a6613e60d Add "workflow_dispatch" to GHEvent
This event is undocumented at this time, but this change will stop the deserialization errors.

Fixes #854
2020-07-01 15:57:33 -07:00
Liam Newman
62e186c123 Merge pull request #871 from hub4j/dependabot/maven/net.revelc.code.formatter-formatter-maven-plugin-2.12.0
Chore(deps): Bump formatter-maven-plugin from 2.11.0 to 2.12.0
2020-06-30 11:02:37 -07:00
Marcos.Cela
50dd8f5bcc add a new doc section "Working with organizations" with a simple example
Closes #812
2020-06-30 09:44:57 +02:00
Marcos Cela
d5fcac9c45 Merge pull request #1 from hub4j/master
Update base
2020-06-30 08:44:42 +02:00
dependabot[bot]
c2bed85190 Chore(deps): Bump formatter-maven-plugin from 2.11.0 to 2.12.0
Bumps [formatter-maven-plugin](https://github.com/revelc/formatter-maven-plugin) from 2.11.0 to 2.12.0.
- [Release notes](https://github.com/revelc/formatter-maven-plugin/releases)
- [Changelog](https://github.com/revelc/formatter-maven-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/revelc/formatter-maven-plugin/compare/formatter-maven-plugin-2.11.0...formatter-maven-plugin-2.12.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-30 02:00:52 +00:00
Liam Newman
183b463ef2 Merge branch 'master' into add-support-for-child-teams 2020-06-29 09:58:14 -07:00
Liam Newman
92fdac44a0 Merge pull request #869 from hub4j/dependabot/maven/org.apache.maven.plugins-maven-site-plugin-3.9.1
Chore(deps): Bump maven-site-plugin from 3.9.0 to 3.9.1
2020-06-29 09:57:00 -07:00
dependabot[bot]
12829ecc73 Chore(deps): Bump maven-site-plugin from 3.9.0 to 3.9.1
Bumps [maven-site-plugin](https://github.com/apache/maven-site-plugin) from 3.9.0 to 3.9.1.
- [Release notes](https://github.com/apache/maven-site-plugin/releases)
- [Commits](https://github.com/apache/maven-site-plugin/compare/maven-site-plugin-3.9.0...maven-site-plugin-3.9.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-25 02:00:46 +00:00
Alexander Kjäll
51319c3b26 use this.organization instead of providing the organization as a parameter 2020-06-24 07:30:59 +02:00
Liam Newman
8fd827040b Merge pull request #860 from hub4j/dependabot/github_actions/actions/cache-v2
Chore(deps): Bump actions/cache from v1 to v2
2020-06-23 09:24:06 -07:00
dependabot[bot]
5ec46eae0d Chore(deps): Bump actions/cache from v1 to v2
Bumps [actions/cache](https://github.com/actions/cache) from v1 to v2.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v1...b8204782bbb5f872091ecc5eb9cb7d004e35b1fa)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-23 14:59:54 +00:00
Liam Newman
32c03301be Merge pull request #853 from sullis/add-dependabot
add Dependabot
2020-06-23 07:59:26 -07:00
Liam Newman
df7f29b2ab Merge pull request #858 from hub4j/dependabot/maven/spotbugs.version-4.0.6
Chore(deps): Bump spotbugs.version from 4.0.4 to 4.0.6
2020-06-23 07:55:48 -07:00
dependabot-preview[bot]
e863113c36 Chore(deps): Bump spotbugs.version from 4.0.4 to 4.0.6
Bumps `spotbugs.version` from 4.0.4 to 4.0.6.

Updates `spotbugs` from 4.0.4 to 4.0.6
- [Release notes](https://github.com/spotbugs/spotbugs/releases)
- [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spotbugs/spotbugs/compare/4.0.4...4.0.6)

Updates `spotbugs-annotations` from 4.0.4 to 4.0.6
- [Release notes](https://github.com/spotbugs/spotbugs/releases)
- [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spotbugs/spotbugs/compare/4.0.4...4.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-23 06:40:11 +00:00
Liam Newman
8e2c1d7382 Merge pull request #851 from MarcosCela/fix/ghteambuilder-accept-long
GHTeamBuilder#parentTeamId now accepts a long instead of an int
2020-06-22 06:28:20 -07:00
Liam Newman
ab7b9cccba Merge branch 'master' into fix/ghteambuilder-accept-long 2020-06-22 06:18:54 -07:00
Liam Newman
81bf61a161 Merge pull request #857 from hub4j/dependabot/maven/com.github.spotbugs-spotbugs-maven-plugin-4.0.4
Chore(deps): Bump spotbugs-maven-plugin from 4.0.0 to 4.0.4
2020-06-22 06:14:39 -07:00
dependabot-preview[bot]
b40f008647 Chore(deps): Bump spotbugs-maven-plugin from 4.0.0 to 4.0.4
Bumps [spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases)
- [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.0.0...spotbugs-maven-plugin-4.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-22 06:57:13 +00:00
Sean C. Sullivan
734e41702b add Dependabot
https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/
2020-06-20 07:10:28 -07:00
Alexander Kjäll
038dd20a91 added a unit test for fetching a child team 2020-06-19 08:27:25 +02:00
Marcos.Cela
1dd62b8550 add a simple test for GHTeamBuilder: create a team with a parent/child relation
Additionally, ensure that when creating the team and setting the parentTeamId
on the GHTeamBuilder, we receive it directly from a previously retrieved
GHTeam. This ensures that the return type of GHTeam#getId() is compatible
with GHTeamBuilder#parentTeamId()
2020-06-18 10:33:04 +02:00
Marcos.Cela
715deebe05 GHTeamBuilder#parentTeamId now accepts a long instead of an int
Closes #850
2020-06-17 12:15:29 +02:00
Alexander Kjäll
b3fe3d8590 Added support for fetching what teams are part of this team.
The call is to this endpoint https://developer.github.com/v3/teams/#list-child-teams-legacy
2020-06-17 09:38:17 +02:00
Liam Newman
f74c3ed3ea Merge pull request #848 from hub4j/dependabot/maven/org.kohsuke.stapler-stapler-1.260
Chore(deps-dev): Bump stapler from 1.259 to 1.260
2020-06-16 08:39:53 -07:00
Liam Newman
2c9aebeeed Merge pull request #847 from hub4j/timja-patch-1
Fix tag template in release drafter
2020-06-16 08:34:54 -07:00
dependabot-preview[bot]
7474f1e11f Chore(deps-dev): Bump stapler from 1.259 to 1.260
Bumps [stapler](https://github.com/stapler/stapler) from 1.259 to 1.260.
- [Release notes](https://github.com/stapler/stapler/releases)
- [Changelog](https://github.com/stapler/stapler/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stapler/stapler/compare/stapler-parent-1.259...stapler-parent-1.260)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-16 06:28:21 +00:00
Tim Jacomb
dba9c55b64 Fix tag template in release drafter 2020-06-16 07:16:52 +01:00
Liam Newman
b432364397 [maven-release-plugin] prepare for next development iteration 2020-06-10 20:02:26 -07:00
71 changed files with 9043 additions and 418 deletions

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
time: "02:00"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
time: "02:00"

View File

@@ -1,5 +1,5 @@
name-template: 'v$NEXT_PATCH_VERSION 🌈'
tag-template: 'v$NEXT_PATCH_VERSION'
tag-template: 'github-api-$NEXT_MINOR_VERSION'
categories:
- title: '🚀 Features'
labels:

View File

@@ -17,7 +17,7 @@ jobs:
with:
java-version: ${{ matrix.java }}
- name: Cached .m2
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@@ -37,7 +37,7 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- uses: actions/cache@v1
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@@ -58,7 +58,7 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- uses: actions/cache@v1
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

54
pom.xml
View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.114</version>
<version>1.115</version>
<name>GitHub API for Java</name>
<url>https://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -11,7 +11,7 @@
<connection>scm:git:git@github.com/hub4j/${project.artifactId}.git</connection>
<developerConnection>scm:git:ssh://git@github.com/hub4j/${project.artifactId}.git</developerConnection>
<url>https://github.com/hub4j/github-api/</url>
<tag>github-api-1.114</tag>
<tag>github-api-1.115</tag>
</scm>
<distributionManagement>
@@ -33,8 +33,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spotbugs-maven-plugin.version>4.0.0</spotbugs-maven-plugin.version>
<spotbugs.version>4.0.4</spotbugs.version>
<spotbugs-maven-plugin.version>4.0.4</spotbugs-maven-plugin.version>
<spotbugs.version>4.0.6</spotbugs.version>
<spotbugs-maven-plugin.failOnError>true</spotbugs-maven-plugin.failOnError>
<hamcrest.version>2.2</hamcrest.version>
<okhttp3.version>4.4.1</okhttp3.version>
@@ -79,6 +79,14 @@
</testResources>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!-- SUREFIRE-1226 workaround -->
<trimStackTrace>false</trimStackTrace>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@@ -233,7 +241,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.9.0</version>
<version>3.9.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -280,16 +288,31 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!-- SUREFIRE-1226 workaround -->
<trimStackTrace>false</trimStackTrace>
</configuration>
<executions>
<execution>
<id>default-test</id>
<configuration>
<excludesFile>src/test/resources/slow-or-flaky-tests.txt</excludesFile>
</configuration>
</execution>
<execution>
<id>slow-or-flaky-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<rerunFailingTestsCount>2</rerunFailingTestsCount>
<!-- There are some tests that take longer or are a little flaky. Run them here. -->
<includesFile>src/test/resources/slow-or-flaky-tests.txt</includesFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.18</version>
<version>1.19</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
@@ -322,7 +345,7 @@
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.11.0</version>
<version>2.12.1</version>
<executions>
<execution>
<goals>
@@ -330,6 +353,7 @@
</goals>
<configuration>
<configFile>src/main/resources/eclipse/formatter.xml</configFile>
<cachedir>${project.build.directory}/.cache</cachedir>
</configuration>
</execution>
</executions>
@@ -445,7 +469,7 @@
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler</artifactId>
<version>1.259</version>
<version>1.260</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -495,7 +519,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<version>3.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -507,7 +531,7 @@
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<version>2.26.3</version>
<version>2.27.1</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -74,8 +74,14 @@ public class GHCheckRun extends GHObject {
return conclusion;
}
/**
* Final conclusion of the check.
*
* From <a href="https://docs.github.com/en/rest/reference/checks#create-a-check-run--parameters">Check Run
* Parameters - <code>conclusion</code></a>.
*/
public static enum Conclusion {
SUCCESS, FAILURE, NEUTRAL, CANCELLED, TIMED_OUT, ACTION_REQUIRED
SUCCESS, FAILURE, NEUTRAL, CANCELLED, TIMED_OUT, ACTION_REQUIRED, SKIPPED
}
/**

View File

@@ -30,6 +30,7 @@ public class GHContent implements Refreshable {
private String sha;
private String name;
private String path;
private String target;
private String content;
private String url; // this is the API url
private String git_url; // this is the Blob url
@@ -99,6 +100,15 @@ public class GHContent implements Refreshable {
return path;
}
/**
* Gets target of a symlink. This will only be set if {@code "symlink".equals(getType())}
*
* @return the target
*/
public String getTarget() {
return target;
}
/**
* Retrieve the decoded content that is stored at this location.
*

View File

@@ -62,6 +62,7 @@ public enum GHEvent {
TEAM,
TEAM_ADD,
WATCH,
WORKFLOW_DISPATCH,
/**
* Special event type that means "every possible event"

View File

@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -29,7 +30,7 @@ public class GHRateLimit {
/**
* Remaining calls that can be made.
*
* @deprecated This value should never have been made public. Use {@link #getRemaining()}
* @deprecated This field should never have been made public. Use {@link #getRemaining()}
*/
@Deprecated
public int remaining;
@@ -37,7 +38,7 @@ public class GHRateLimit {
/**
* Allotted API call per hour.
*
* @deprecated This value should never have been made public. Use {@link #getLimit()}
* @deprecated This field should never have been made public. Use {@link #getLimit()}
*/
@Deprecated
public int limit;
@@ -48,7 +49,7 @@ public class GHRateLimit {
* date. To use this field in any meaningful way, it must be converted to a long using {@link Date#getTime()}
* multiplied by 1000.
*
* @deprecated This value should never have been made public. Use {@link #getResetDate()}
* @deprecated This field should never have been made public. Use {@link #getResetDate()}
*/
@Deprecated
public Date reset;
@@ -65,17 +66,58 @@ public class GHRateLimit {
@Nonnull
private final Record integrationManifest;
/**
* The default GHRateLimit provided to new {@link GitHubClient}s.
*
* Contains all expired records that will cause {@link GitHubClient#rateLimit(RateLimitTarget)} to refresh with new
* data when called.
*
* Private, but made internal for testing.
*/
@Nonnull
static GHRateLimit Unknown() {
return new GHRateLimit(new UnknownLimitRecord(),
new UnknownLimitRecord(),
new UnknownLimitRecord(),
new UnknownLimitRecord());
}
static final GHRateLimit DEFAULT = new GHRateLimit(UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT);
/**
* Creates a new {@link GHRateLimit} from a single record for the specified endpoint with place holders for other
* records.
*
* This is used to create {@link GHRateLimit} instances that can merged with other instances.
*
* @param record
* the rate limit record. Can be a regular {@link Record} constructed from header information or an
* {@link UnknownLimitRecord} placeholder.
* @param rateLimitTarget
* which rate limit record to fill
* @return a new {@link GHRateLimit} instance containing the supplied record
*/
@Nonnull
static GHRateLimit fromHeaderRecord(Record header) {
return new GHRateLimit(header, new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord());
static GHRateLimit fromRecord(@Nonnull Record record, @Nonnull RateLimitTarget rateLimitTarget) {
if (rateLimitTarget == RateLimitTarget.CORE || rateLimitTarget == RateLimitTarget.NONE) {
return new GHRateLimit(record,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT);
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
record,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT);
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
record,
UnknownLimitRecord.DEFAULT);
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
return new GHRateLimit(UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
UnknownLimitRecord.DEFAULT,
record);
} else {
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
}
}
@JsonCreator
@@ -142,7 +184,7 @@ public class GHRateLimit {
}
/**
* Whether the rate limit reset date for this instance has passed.
* Whether the reset date for the Core API rate limit has passed.
*
* @return true if the rate limit reset date has passed. Otherwise false.
* @since 1.100
@@ -152,7 +194,7 @@ public class GHRateLimit {
}
/**
* The core object provides your rate limit status for all non-search-related resources in the REST API.
* The core object provides the rate limit status for all non-search-related resources in the REST API.
*
* @return a rate limit record
* @since 1.100
@@ -163,42 +205,43 @@ public class GHRateLimit {
}
/**
* The search object provides your rate limit status for the Search API. TODO: integrate with header limit updating.
* Issue #605.
* The search record provides the rate limit status for the Search API.
*
* @return a rate limit record
* @since 1.115
*/
@Nonnull
Record getSearch() {
public Record getSearch() {
return search;
}
/**
* The graphql object provides your rate limit status for the GraphQL API. TODO: integrate with header limit
* updating. Issue #605.
* The graphql record provides the rate limit status for the GraphQL API.
*
* @return a rate limit record
* @since 1.115
*/
@Nonnull
Record getGraphQL() {
public Record getGraphQL() {
return graphql;
}
/**
* The integration_manifest object provides your rate limit status for the GitHub App Manifest code conversion
* endpoint. TODO: integrate with header limit updating. Issue #605.
* The integration manifest record provides the rate limit status for the GitHub App Manifest code conversion
* endpoint.
*
* @return a rate limit record
* @since 1.115
*/
@Nonnull
Record getIntegrationManifest() {
public Record getIntegrationManifest() {
return integrationManifest;
}
@Override
public String toString() {
return "GHRateLimit {" + "core " + getCore().toString() + "search " + getSearch().toString() + "graphql "
+ getGraphQL().toString() + "integrationManifest " + getIntegrationManifest().toString() + '}';
return "GHRateLimit {" + "core " + getCore().toString() + ", search " + getSearch().toString() + ", graphql "
+ getGraphQL().toString() + ", integrationManifest " + getIntegrationManifest().toString() + "}";
}
@Override
@@ -221,44 +264,111 @@ public class GHRateLimit {
}
/**
* Gets the appropriate {@link Record} for a particular url path.
*
* @param urlPath
* the url path of the request
* @return the {@link Record} for a url path.
* Merge a {@link GHRateLimit} with another one to create a new {@link GHRateLimit} keeping the latest
* {@link Record}s from each.
*
* @param newLimit
* {@link GHRateLimit} with potentially updated {@link Record}s.
* @return a merged {@link GHRateLimit} with the latest {@link Record}s from these two instances. If the merged
* instance is equal to the current instance, the current instance is returned.
*/
@Nonnull
Record getRecordForUrlPath(@Nonnull String urlPath) {
if (urlPath.equals("/rate_limit")) {
return new UnknownLimitRecord();
} else if (urlPath.startsWith("/search")) {
return getSearch();
} else if (urlPath.startsWith("/graphql")) {
return getGraphQL();
} else if (urlPath.startsWith("/app-manifests")) {
return getIntegrationManifest();
} else {
GHRateLimit getMergedRateLimit(@Nonnull GHRateLimit newLimit) {
GHRateLimit merged = new GHRateLimit(getCore().currentOrUpdated(newLimit.getCore()),
getSearch().currentOrUpdated(newLimit.getSearch()),
getGraphQL().currentOrUpdated(newLimit.getGraphQL()),
getIntegrationManifest().currentOrUpdated(newLimit.getIntegrationManifest()));
if (merged.equals(this)) {
merged = this;
}
return merged;
}
/**
* Gets the specified {@link Record}.
*
* {@link RateLimitTarget#NONE} will return {@link UnknownLimitRecord#DEFAULT} to prevent any clients from
* accidentally waiting on that record to reset before continuing.
*
* @param rateLimitTarget
* the target rate limit record
* @return the target {@link Record} from this instance.
*/
@Nonnull
Record getRecord(@Nonnull RateLimitTarget rateLimitTarget) {
if (rateLimitTarget == RateLimitTarget.CORE) {
return getCore();
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
return getSearch();
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
return getGraphQL();
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
return getIntegrationManifest();
} else if (rateLimitTarget == RateLimitTarget.NONE) {
return UnknownLimitRecord.DEFAULT;
} else {
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
}
}
/**
* A limit record used as a placeholder when the the actual limit is not known.
* <p>
* Has a large limit and long duration so that it will doesn't expire too often.
*
* @since 1.100
*/
public static class UnknownLimitRecord extends Record {
// One hour
private static final long unknownLimitResetSeconds = 60L * 60L;
private static final long defaultUnknownLimitResetSeconds = Duration.ofSeconds(30).getSeconds();
/**
* The number of seconds until a {@link UnknownLimitRecord} will expire.
*
* This is set to a somewhat short duration, rather than a long one. This avoids
* {@link {@link GitHubClient#rateLimit(RateLimitTarget)}} requesting rate limit updates continuously, but also
* avoids holding on to stale unknown records indefinitely.
*
* When merging {@link GHRateLimit} instances, {@link UnknownLimitRecord}s will be superseded by incoming
* regular {@link Record}s.
*
* @see GHRateLimit#getMergedRateLimit(GHRateLimit)
*/
static long unknownLimitResetSeconds = defaultUnknownLimitResetSeconds;
static final int unknownLimit = 1000000;
static final int unknownRemaining = 999999;
private UnknownLimitRecord() {
super(unknownLimit, unknownRemaining, System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
// The default UnknownLimitRecord is an expired record.
private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE);
// The starting current UnknownLimitRecord is an expired record.
private static UnknownLimitRecord current = DEFAULT;
/**
* Create a new unknown record that resets at the specified time.
*
* @param resetEpochSeconds
* the epoch second time when this record will expire.
*/
private UnknownLimitRecord(long resetEpochSeconds) {
super(unknownLimit, unknownRemaining, resetEpochSeconds);
}
static synchronized Record current() {
if (current.isExpired()) {
current = new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
}
return current;
}
/**
* Reset the current UnknownLimitRecord. For use during testing only.
*/
static synchronized void reset() {
current = DEFAULT;
unknownLimitResetSeconds = defaultUnknownLimitResetSeconds;
}
}
@@ -274,14 +384,12 @@ public class GHRateLimit {
private final int remaining;
/**
* Allotted API call per hour.
* Allotted API call per time period.
*/
private final int limit;
/**
* The time at which the current rate limit window resets in UTC epoch seconds.
*
* This is the raw value returned by the server.
*/
private final long resetEpochSeconds;
@@ -291,9 +399,11 @@ public class GHRateLimit {
private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000;
/**
* The time at which the rate limit will reset. This value is calculated based on
* {@link #getResetEpochSeconds()} by calling {@link #calculateResetDate}. If the clock on the local machine not
* synchronized with the server clock, this time value will be adjusted to match the local machine's clock.
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
* synchronized with to the same clock as the GitHub server.
*
* @see #calculateResetDate(String)
* @see #getResetDate()
*/
@Nonnull
private final Date resetDate;
@@ -341,12 +451,58 @@ public class GHRateLimit {
this.resetDate = calculateResetDate(updatedAt);
}
/**
* Determine if the current {@link Record} is outdated compared to another. Rate Limit dates are only accurate
* to the second, so we look at other information in the record as well.
*
* {@link Record}s with earlier {@link #getResetEpochSeconds()} are replaced by those with later.
* {@link Record}s with the same {@link #getResetEpochSeconds()} are replaced by those with less remaining
* count.
*
* {@link UnknownLimitRecord}s compare with each other like regular {@link Record}s.
*
* {@link Record}s are replaced by {@link UnknownLimitRecord}s only when the current {@link Record} is expired
* and the {@link UnknownLimitRecord} is not. Otherwise Regular {@link Record}s are not replaced by
* {@link UnknownLimitRecord}s.
*
* Expiration is only considered after other checks, meaning expired records may sometimes be replaced by other
* expired records.
*
* @param other
* the other {@link Record}
* @return the {@link Record} that is most current
*/
Record currentOrUpdated(@Nonnull Record other) {
// This set of checks avoids most calls to isExpired()
// Depends on UnknownLimitRecord.current() to prevent continuous updating of GHRateLimit rateLimit()
if (getResetEpochSeconds() > other.getResetEpochSeconds()
|| (getResetEpochSeconds() == other.getResetEpochSeconds()
&& getRemaining() <= other.getRemaining())) {
// If the current record has a later reset
// or the current record has the same reset and fewer or same requests remaining
// Then it is most recent
return this;
} else if (!(other instanceof UnknownLimitRecord)) {
// If the above is not the case that means other has a later reset
// or the same resent and fewer requests remaining.
// If the other record is not an unknown record, the the other is more recent
return other;
} else if (this.isExpired() && !other.isExpired()) {
// The other is an unknown record.
// If the current record has expired and the other hasn't, return the other.
return other;
}
// If none of the above, the current record is most valid.
return this;
}
/**
* Recalculates the {@link #resetDate} relative to the local machine clock.
* <p>
* {@link RateLimitChecker}s and {@link RateLimitHandler}s use {@link #getResetDate()} to make decisions about
* how long to wait for until for the rate limit to reset. That means that {@link #getResetDate()} needs to be
* accurate to the local machine.
* calculated based on the local machine clock.
* </p>
* <p>
* When we say that the clock on two machines is "synchronized", we mean that the UTC time returned from
@@ -415,7 +571,7 @@ public class GHRateLimit {
* {@link #getResetDate()} or implement a {@link RateLimitChecker} instead.
*
* @return a long representing the time in epoch seconds when the rate limit will reset
* @see #getResetDate() #getResetDate()
* @see #getResetDate()
*/
public long getResetEpochSeconds() {
return resetEpochSeconds;
@@ -424,6 +580,8 @@ public class GHRateLimit {
/**
* Whether the rate limit reset date indicated by this instance is expired
*
* If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead.
*
* @return true if the rate limit reset date has passed. Otherwise false.
*/
public boolean isExpired() {
@@ -431,8 +589,8 @@ public class GHRateLimit {
}
/**
* Returns the date at which the rate limit will reset, adjusted to local machine time if the local machine's
* clock not synchronized with to the same clock as the GitHub server.
* The date at which the rate limit will reset, adjusted to local machine time if the local machine's clock not
* synchronized with to the same clock as the GitHub server.
*
* If attempting to wait for the rate limit to reset, consider implementing a {@link RateLimitChecker} instead.
*

View File

@@ -1,5 +1,6 @@
package org.kohsuke.github;
import com.fasterxml.jackson.databind.JsonMappingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
@@ -90,6 +91,78 @@ public class GHRef {
return this;
}
/**
* Retrive a ref of the given type for the current GitHub repository.
*
* @param repository
* the repository to read from
* @param refName
* eg: heads/branch
* @return refs matching the request type
* @throws IOException
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
static GHRef read(GHRepository repository, String refName) throws IOException {
// Also accept e.g. "refs/heads/branch" for consistency with createRef().
if (refName.startsWith("refs/")) {
refName = refName.replaceFirst("refs/", "");
}
// We would expect this to use `git/ref/%s` but some versions of GHE seem to not support it
// Instead use `git/refs/%s` and check the result actually matches the ref
GHRef result = null;
try {
result = repository.root.createRequest()
.withUrlPath(repository.getApiTailUrl(String.format("git/refs/%s", refName)))
.fetch(GHRef.class)
.wrap(repository.root);
} catch (IOException e) {
// If the parse exception is due to the above returning an array instead of a single ref
// that means the individual ref did not exist. Handled by result check below.
// Otherwise, rethrow.
if (!(e.getCause() instanceof JsonMappingException)) {
throw e;
}
}
// Verify that the ref returned is the one requested
// Used .endsWith(refName) instead of .equals("refs/" + refName) to workaround a GitBucket
// issue where the "ref" field omits the "refs/" prefix. "endsWith()" is functionally
// the same for this scenario - the server refs matching is prefix-based, so
// a ref that ends with the correct string will always be the correct one.
if (result == null || !result.getRef().endsWith(refName)) {
throw new GHFileNotFoundException(String.format("git/refs/%s", refName)
+ " {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}");
}
return result;
}
/**
* Retrieves all refs of the given type for the current GitHub repository.
*
* @param repository
* the repository to read from
* @param refType
* the type of reg to search for e.g. <code>tags</code> or <code>commits</code>
* @return paged iterable of all refs of the specified type
* @throws IOException
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
static PagedIterable<GHRef> readMatching(GHRepository repository, String refType) throws IOException {
if (refType.startsWith("refs/")) {
refType = refType.replaceFirst("refs/", "");
}
String url = repository.getApiTailUrl(String.format("git/refs/%s", refType));
// if no types, do not end with slash just to be safe.
if (refType.equals("")) {
url = url.substring(0, url.length() - 1);
}
return repository.root.createRequest()
.withUrlPath(url)
.toIterable(GHRef[].class, item -> item.wrap(repository.root));
}
/**
* The type GHObject.
*/

View File

@@ -25,7 +25,6 @@ package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -1541,8 +1540,7 @@ public class GHRepository extends GHObject {
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public PagedIterable<GHRef> listRefs() throws IOException {
final String url = String.format("/repos/%s/%s/git/refs", getOwnerName(), name);
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
return listRefs("");
}
/**
@@ -1568,11 +1566,7 @@ public class GHRepository extends GHObject {
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public PagedIterable<GHRef> listRefs(String refType) throws IOException {
if (refType.startsWith("refs/")) {
refType = refType.replaceFirst("refs/", "");
}
final String url = String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType);
return root.createRequest().withUrlPath(url).toIterable(GHRef[].class, item -> item.wrap(root));
return GHRef.readMatching(this, refType);
}
/**
@@ -1585,34 +1579,7 @@ public class GHRepository extends GHObject {
* on failure communicating with GitHub, potentially due to an invalid ref type being requested
*/
public GHRef getRef(String refName) throws IOException {
// Also accept e.g. "refs/heads/branch" for consistency with createRef().
if (refName.startsWith("refs/")) {
refName = refName.replaceFirst("refs/", "");
}
// We would expect this to use `git/ref/%s` but some versions of GHE seem to not support it
// Instead use `git/refs/%s` and check the result actually matches the ref
GHRef result = null;
try {
result = root.createRequest()
.withUrlPath(getApiTailUrl(String.format("git/refs/%s", refName)))
.fetch(GHRef.class)
.wrap(root);
} catch (IOException e) {
// If the parse exception is due to the above returning an array instead of a single ref
// that means the individual ref did not exist
if (!(e.getCause() instanceof JsonMappingException)) {
throw e;
}
}
// Verify that the ref returned is the one requested
if (result == null || !result.getRef().equals("refs/" + refName)) {
throw new GHFileNotFoundException(String.format("git/refs/%s", refName)
+ " {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/git/refs/#get-a-reference\"}");
}
return result;
return GHRef.read(this, refName);
}
/**

View File

@@ -25,6 +25,7 @@ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> {
super(root);
this.receiverType = receiverType;
req.withUrlPath(getApiUrl());
req.rateLimit(RateLimitTarget.SEARCH);
}
/**

View File

@@ -174,6 +174,19 @@ public class GHTeam extends GHObject implements Refreshable {
return root.createRequest().withUrlPath(api("/members")).toIterable(GHUser[].class, item -> item.wrapUp(root));
}
/**
* Retrieves the teams that are children of this team.
*
* @return the paged iterable
* @throws IOException
* the io exception
*/
public PagedIterable<GHTeam> listChildTeams() throws IOException {
return root.createRequest()
.withUrlPath(api("/teams"))
.toIterable(GHTeam[].class, item -> item.wrapUp(this.organization));
}
/**
* Gets members.
*

View File

@@ -75,7 +75,7 @@ public class GHTeamBuilder {
* parentTeamId of team
* @return a builder to continue with building
*/
public GHTeamBuilder parentTeamId(int parentTeamId) {
public GHTeamBuilder parentTeamId(long parentTeamId) {
this.builder.with("parent_team_id", parentTeamId);
return this;
}

View File

@@ -373,12 +373,20 @@ public class GitHub {
}
/**
* Gets the current rate limit.
* Gets the current full rate limit information from the server.
*
* For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that
* case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned
* in the response header for this request in if was present.
*
* For most use cases it would be better to implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*
* @return the rate limit
* @throws IOException
* the io exception
*/
@Nonnull
public GHRateLimit getRateLimit() throws IOException {
return client.getRateLimit();
}
@@ -388,8 +396,11 @@ public class GitHub {
* GitHub Enterprise) or if no requests have been made.
*
* @return the most recently observed rate limit data or {@code null}.
* @deprecated implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*/
@CheckForNull
@Nonnull
@Deprecated
public GHRateLimit lastRateLimit() {
return client.lastRateLimit();
}
@@ -400,10 +411,13 @@ public class GitHub {
* @return the current rate limit data.
* @throws IOException
* if we couldn't get the current rate limit data.
* @deprecated implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*/
@Nonnull
@Deprecated
public GHRateLimit rateLimit() throws IOException {
return client.rateLimit();
return client.rateLimit(RateLimitTarget.CORE);
}
/**

View File

@@ -358,6 +358,18 @@ public class GitHubBuilder implements Cloneable {
return this;
}
/**
* Adds a {@link RateLimitChecker} for the Core API for this {@link GitHubBuilder}.
*
* @param coreRateLimitChecker
* the {@link RateLimitChecker} for core GitHub API requests
* @return the git hub builder
* @see #withRateLimitChecker(RateLimitChecker, RateLimitTarget)
*/
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker coreRateLimitChecker) {
return withRateLimitChecker(coreRateLimitChecker, RateLimitTarget.CORE);
}
/**
* Adds a {@link RateLimitChecker} to this {@link GitHubBuilder}.
* <p>
@@ -376,15 +388,15 @@ public class GitHubBuilder implements Cloneable {
* request.
* </p>
*
* @param coreRateLimitChecker
* the {@link RateLimitChecker} for core GitHub API requests
* @param rateLimitChecker
* the {@link RateLimitChecker} for requests
* @param rateLimitTarget
* the {@link RateLimitTarget} specifying which rate limit record to check
* @return the git hub builder
*/
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker coreRateLimitChecker) {
this.rateLimitChecker = new GitHubRateLimitChecker(coreRateLimitChecker,
RateLimitChecker.NONE,
RateLimitChecker.NONE,
RateLimitChecker.NONE);
public GitHubBuilder withRateLimitChecker(@Nonnull RateLimitChecker rateLimitChecker,
@Nonnull RateLimitTarget rateLimitTarget) {
this.rateLimitChecker = this.rateLimitChecker.with(rateLimitChecker, rateLimitTarget);
return this;
}

View File

@@ -9,7 +9,6 @@ import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -45,7 +44,7 @@ import static java.util.logging.Level.*;
* A GitHub API Client
* <p>
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link #rateLimit()}.
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link GHRateLimit}.
* </p>
*/
abstract class GitHubClient {
@@ -71,9 +70,10 @@ abstract class GitHubClient {
private HttpConnector connector;
private final Object headerRateLimitLock = new Object();
private GHRateLimit headerRateLimit = null;
private volatile GHRateLimit rateLimit = null;
private final Object rateLimitLock = new Object();
@Nonnull
private GHRateLimit rateLimit = GHRateLimit.DEFAULT;
private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
@@ -142,10 +142,8 @@ abstract class GitHubClient {
}
private <T> T fetch(Class<T> type, String urlPath) throws IOException {
return this
.sendRequest(GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build(),
(responseInfo) -> GitHubResponse.parseBody(responseInfo, type))
.body();
GitHubRequest request = GitHubRequest.newBuilder().withApiUrl(getApiUrl()).withUrlPath(urlPath).build();
return this.sendRequest(request, (responseInfo) -> GitHubResponse.parseBody(responseInfo, type)).body();
}
/**
@@ -209,11 +207,14 @@ abstract class GitHubClient {
}
/**
* Gets the current rate limit from the server.
* Gets the current full rate limit information from the server.
*
* For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In
* that, if {@link #lastRateLimit()} is not {@code null} and is not expired, it will be returned. Otherwise, a
* placeholder {@link GHRateLimit} instance with {@link GHRateLimit.UnknownLimitRecord}s will be returned.
* For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that
* case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned
* in the response header for this request in if was present.
*
* For most use cases it would be better to implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*
* @return the rate limit
* @throws IOException
@@ -221,59 +222,95 @@ abstract class GitHubClient {
*/
@Nonnull
public GHRateLimit getRateLimit() throws IOException {
return getRateLimit(RateLimitTarget.NONE);
}
@Nonnull
GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
GHRateLimit result;
try {
result = fetch(JsonRateLimit.class, "/rate_limit").resources;
GitHubRequest request = GitHubRequest.newBuilder()
.rateLimit(RateLimitTarget.NONE)
.withApiUrl(getApiUrl())
.withUrlPath("/rate_limit")
.build();
result = this
.sendRequest(request, (responseInfo) -> GitHubResponse.parseBody(responseInfo, JsonRateLimit.class))
.body().resources;
} catch (FileNotFoundException e) {
// For some versions of GitHub Enterprise, the rate_limit endpoint returns a 404.
LOGGER.log(FINE, "/rate_limit returned 404 Not Found.");
// However some newer versions of GHE include rate limit header information
// Use that if available
result = lastRateLimit();
if (result == null || result.isExpired()) {
// return a default rate limit
result = GHRateLimit.Unknown();
}
// If the header info is missing and the endpoint returns 404, fill the rate limit
// with unknown
result = GHRateLimit.fromRecord(GHRateLimit.UnknownLimitRecord.current(), rateLimitTarget);
}
return rateLimit = result;
return updateRateLimit(result);
}
/**
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example
* GitHub Enterprise) or if no requests have been made.
* Returns the most recently observed rate limit data.
*
* @return the most recently observed rate limit data or {@code null}.
* Generally, instead of calling this you should implement a {@link RateLimitChecker} or call
*
* @return the most recently observed rate limit data. This may include expired or
* {@link GHRateLimit.UnknownLimitRecord} entries.
* @deprecated implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*/
@CheckForNull
public GHRateLimit lastRateLimit() {
synchronized (headerRateLimitLock) {
return headerRateLimit;
@Nonnull
@Deprecated
GHRateLimit lastRateLimit() {
synchronized (rateLimitLock) {
return rateLimit;
}
}
/**
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
* Gets the current rate limit for an endpoint while trying not to actually make any remote requests unless
* absolutely necessary.
*
* If {@link #lastRateLimit()} is not {@code null} and is not expired, it will be returned. If the information
* returned from the last call to {@link #getRateLimit()} is not {@code null} and is not expired, then it will be
* returned. Otherwise, the result of a call to {@link #getRateLimit()} will be returned.
* If the {@link GHRateLimit.Record} for {@code urlPath} is not expired, it is returned. If the
* {@link GHRateLimit.Record} for {@code urlPath} is expired, {@link #getRateLimit()} will be called to get the
* current rate limit.
*
* @return the current rate limit data.
* @param rateLimitTarget
* the endpoint to get the rate limit for.
*
* @return the current rate limit data. {@link GHRateLimit.Record}s in this instance may be expired when returned.
* @throws IOException
* if there was an error getting current rate limit data.
*/
@Nonnull
public GHRateLimit rateLimit() throws IOException {
synchronized (headerRateLimitLock) {
if (headerRateLimit != null && !headerRateLimit.isExpired()) {
return headerRateLimit;
GHRateLimit rateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
synchronized (rateLimitLock) {
if (rateLimit.getRecord(rateLimitTarget).isExpired()) {
getRateLimit(rateLimitTarget);
}
return rateLimit;
}
GHRateLimit result = this.rateLimit;
if (result == null || result.isExpired()) {
result = getRateLimit();
}
/**
* Update the Rate Limit with the latest info from response header.
*
* Due to multi-threading, requests might complete out of order. This method calls
* {@link GHRateLimit#getMergedRateLimit(GHRateLimit)} to ensure the most current records are used.
*
* @param observed
* {@link GHRateLimit.Record} constructed from the response header information
*/
private GHRateLimit updateRateLimit(@Nonnull GHRateLimit observed) {
synchronized (rateLimitLock) {
observed = rateLimit.getMergedRateLimit(observed);
if (rateLimit != observed) {
rateLimit = observed;
LOGGER.log(FINE, "Rate limit now: {0}", rateLimit);
}
return rateLimit;
}
return result;
}
/**
@@ -517,58 +554,25 @@ abstract class GitHubClient {
}
private void noteRateLimit(@Nonnull GitHubResponse.ResponseInfo responseInfo) {
if (responseInfo.request().urlPath().startsWith("/search")) {
// the search API uses a different rate limit
return;
}
String limitString = responseInfo.headerField("X-RateLimit-Limit");
if (StringUtils.isBlank(limitString)) {
// if we are missing a header, return fast
return;
}
String remainingString = responseInfo.headerField("X-RateLimit-Remaining");
if (StringUtils.isBlank(remainingString)) {
// if we are missing a header, return fast
return;
}
String resetString = responseInfo.headerField("X-RateLimit-Reset");
if (StringUtils.isBlank(resetString)) {
// if we are missing a header, return fast
return;
}
int limit, remaining;
long reset;
try {
String limitString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Limit"),
"Missing X-RateLimit-Limit");
String remainingString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Remaining"),
"Missing X-RateLimit-Remaining");
String resetString = Objects.requireNonNull(responseInfo.headerField("X-RateLimit-Reset"),
"Missing X-RateLimit-Reset");
int limit, remaining;
long reset;
limit = Integer.parseInt(limitString);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limitString, e);
}
return;
}
try {
remaining = Integer.parseInt(remainingString);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remainingString, e);
}
return;
}
try {
reset = Long.parseLong(resetString);
} catch (NumberFormatException e) {
GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, responseInfo);
updateRateLimit(GHRateLimit.fromRecord(observed, responseInfo.request().rateLimitTarget()));
} catch (NumberFormatException | NullPointerException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + resetString, e);
LOGGER.log(FINEST, "Missing or malformed X-RateLimit header: ", e);
}
return;
}
GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, responseInfo);
updateCoreRateLimit(observed);
}
private static void detectOTPRequired(@Nonnull GitHubResponse.ResponseInfo responseInfo) throws GHIOException {
@@ -588,23 +592,6 @@ abstract class GitHubClient {
"This operation requires a credential but none is given to the GitHub constructor");
}
/**
* Update the Rate Limit with the latest info from response header. Due to multi-threading requests might complete
* out of order, we want to pick the one with the most recent info from the server. Calls
* {@link #shouldReplace(GHRateLimit.Record, GHRateLimit.Record)}
*
* @param observed
* {@link GHRateLimit.Record} constructed from the response header information
*/
private void updateCoreRateLimit(@Nonnull GHRateLimit.Record observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null || shouldReplace(observed, headerRateLimit.getCore())) {
headerRateLimit = GHRateLimit.fromHeaderRecord(observed);
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
}
}
private static class GHApiInfo {
private String rate_limit_url;
@@ -654,37 +641,6 @@ abstract class GitHubClient {
}
}
/**
* Determine if one {@link GHRateLimit.Record} should replace another. Header date is only accurate to the second,
* so we look at the information in the record itself.
*
* {@link GHRateLimit.UnknownLimitRecord}s are always replaced by regular {@link GHRateLimit.Record}s. Regular
* {@link GHRateLimit.Record}s are never replaced by {@link GHRateLimit.UnknownLimitRecord}s. Candidates with
* resetEpochSeconds later than current record are more recent. Candidates with the same reset and a lower remaining
* count are more recent. Candidates with an earlier reset are older.
*
* @param candidate
* {@link GHRateLimit.Record} constructed from the response header information
* @param current
* the current {@link GHRateLimit.Record} record
*/
static boolean shouldReplace(@Nonnull GHRateLimit.Record candidate, @Nonnull GHRateLimit.Record current) {
if (candidate instanceof GHRateLimit.UnknownLimitRecord
&& !(current instanceof GHRateLimit.UnknownLimitRecord)) {
// Unknown candidate never replaces a regular record
return false;
} else if (current instanceof GHRateLimit.UnknownLimitRecord
&& !(candidate instanceof GHRateLimit.UnknownLimitRecord)) {
// Any real record should replace an unknown Record.
return true;
} else {
// records of the same type compare to each other as normal.
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds()
&& current.getRemaining() > candidate.getRemaining());
}
}
static URL parseURL(String s) {
try {
return s == null ? null : new URL(s);

View File

@@ -24,7 +24,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
* A GitHub API Client for HttpUrlConnection
* <p>
* A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link #rateLimit()}.
* to send multiple requests. GitHubClient also track some GitHub API information such as {@link GHRateLimit}.
* </p>
* <p>
* GitHubHttpUrlConnectionClient gets a new {@link HttpURLConnection} for each call to send.

View File

@@ -3,26 +3,28 @@ package org.kohsuke.github;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Objects;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
/**
* A GitHub API Rate Limit Checker called before each request. This class provides the basic infrastructure for calling
* the appropriate {@link RateLimitChecker} for a request and retrying as many times as needed. This class supports more
* complex throttling strategies and polling, but leaves the specifics to the {@link RateLimitChecker} implementations.
* A GitHub API Rate Limit Checker called before each request.
*
* <p>
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
* number of requests remaining is returned in the response header and can also be requested using
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
* remaining and the time when the number will be reset is returned in the response header and can also be requested
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
* </p>
* <p>
* GitHub prefers that clients stop before exceeding their rate limit rather than stopping after they exceed it. The
* {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are
* met.
* Different parts of the GitHub API have separate rate limits, but most of REST API uses {@link RateLimitTarget#CORE}.
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit. GitHub prefers that
* clients stop before exceeding their rate limit rather than stopping after they exceed it.
* </p>
* <p>
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit, but each {@link GitHub}
* instance will attempt to cache and reuse the last see rate limit rather than making a new request.
* This class provides the infrastructure for calling the appropriate {@link RateLimitChecker} before each request and
* retrying than call many times as needed. Each {@link RateLimitChecker} decides whether to wait and for how long. This
* allows for a wide range of {@link RateLimitChecker} implementations, including complex throttling strategies and
* polling.
* </p>
*/
class GitHubRateLimitChecker {
@@ -39,6 +41,8 @@ class GitHubRateLimitChecker {
@Nonnull
private final RateLimitChecker integrationManifest;
private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName());
GitHubRateLimitChecker() {
this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);
}
@@ -48,40 +52,57 @@ class GitHubRateLimitChecker {
@Nonnull RateLimitChecker graphql,
@Nonnull RateLimitChecker integrationManifest) {
this.core = Objects.requireNonNull(core);
// for now only support rate limiting on core
// remove these asserts when that changes
assert search == RateLimitChecker.NONE;
assert graphql == RateLimitChecker.NONE;
assert integrationManifest == RateLimitChecker.NONE;
this.search = Objects.requireNonNull(search);
this.graphql = Objects.requireNonNull(graphql);
this.integrationManifest = Objects.requireNonNull(integrationManifest);
}
/**
* Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target.
*
* Only one {@link RateLimitChecker} is allowed per target.
*
* @param checker
* the {@link RateLimitChecker} to apply.
* @param rateLimitTarget
* the {@link RateLimitTarget} for this checker. If {@link RateLimitTarget#NONE}, checker will be ignored
* and no change will be made.
* @return a new {@link GitHubRateLimitChecker}
*/
GitHubRateLimitChecker with(@Nonnull RateLimitChecker checker, @Nonnull RateLimitTarget rateLimitTarget) {
return new GitHubRateLimitChecker(rateLimitTarget == RateLimitTarget.CORE ? checker : core,
rateLimitTarget == RateLimitTarget.SEARCH ? checker : search,
rateLimitTarget == RateLimitTarget.GRAPHQL ? checker : graphql,
rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST ? checker : integrationManifest);
}
/**
* Checks whether there is sufficient requests remaining within this client's rate limit quota to make the current
* request.
* <p>
* This method does not do the actual check. Instead it select the appropriate {@link RateLimitChecker} and
* {@link GHRateLimit.Record} for the current request's urlPath. If the {@link RateLimitChecker} for this the
* current request's urlPath is {@link RateLimitChecker#NONE} the rate limit is not checked. If not, it calls
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}. which decides if the rate limit has been
* exceeded and then sleeps for as long is it choose.
* This method does not do the actual check. Instead it selects the appropriate {@link RateLimitChecker} and
* {@link GHRateLimit.Record} for the current request's {@link RateLimitTarget}. It then calls
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}.
* </p>
* <p>
* It is up to the {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} which decide if the rate limit
* has been exceeded. If it has, that method will sleep for as long is it chooses and then return {@code true}. If
* not, that method will return {@code false}.
* It is up to {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to which decide if the rate limit
* has been exceeded. If it has, {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} will sleep for as
* long is it chooses and then return {@code true}. If not, that method will return {@code false}.
* </p>
* <p>
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
* will request updated rate limit information and call
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows implementers of
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to apply any number of strategies to
* controlling the speed at which requests are made. When it returns {@code false} this method will return and the
* request will be sent.
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows different
* {@link RateLimitChecker} implementations to apply any number of strategies to controlling the speed at which
* requests are made.
* </p>
* <p>
* When the {@link RateLimitChecker} returns {@code false} this method will return and the request processing will
* continue.
* </p>
* <p>
* If the {@link RateLimitChecker} for this the current request's urlPath is {@link RateLimitChecker#NONE} the rate
* limit is not checked.
* </p>
*
* @param client
@@ -92,14 +113,14 @@ class GitHubRateLimitChecker {
* if there is an I/O error
*/
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
RateLimitChecker guard = selectChecker(request.urlPath());
RateLimitChecker guard = selectChecker(request.rateLimitTarget());
if (guard == RateLimitChecker.NONE) {
return;
}
// For the first rate limit, accept the current limit if a valid one is already present.
GHRateLimit rateLimit = client.rateLimit();
GHRateLimit.Record rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
GHRateLimit rateLimit = client.rateLimit(request.rateLimitTarget());
GHRateLimit.Record rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
long waitCount = 0;
try {
while (guard.checkRateLimit(rateLimitRecord, waitCount)) {
@@ -112,8 +133,8 @@ class GitHubRateLimitChecker {
Thread.sleep(1000);
// After the first wait, always request a new rate limit from the server.
rateLimit = client.getRateLimit();
rateLimitRecord = rateLimit.getRecordForUrlPath(request.urlPath());
rateLimit = client.getRateLimit(request.rateLimitTarget());
rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
}
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException(e.getMessage()).initCause(e);
@@ -121,25 +142,28 @@ class GitHubRateLimitChecker {
}
/**
* Gets the appropriate {@link RateLimitChecker} for a particular url path. Similar to
* {@link GHRateLimit#getRecordForUrlPath(String)}.
* Gets the appropriate {@link RateLimitChecker} for a particular target.
*
* @param urlPath
* the url path of the request
* @return the {@link RateLimitChecker} for a url path.
* Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}.
*
* @param rateLimitTarget
* the rate limit to check
* @return the {@link RateLimitChecker} for a particular target
*/
@Nonnull
private RateLimitChecker selectChecker(@Nonnull String urlPath) {
if (urlPath.equals("/rate_limit")) {
private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) {
if (rateLimitTarget == RateLimitTarget.NONE) {
return RateLimitChecker.NONE;
} else if (urlPath.startsWith("/search")) {
} else if (rateLimitTarget == RateLimitTarget.CORE) {
return core;
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
return search;
} else if (urlPath.startsWith("/graphql")) {
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
return graphql;
} else if (urlPath.startsWith("/app-manifests")) {
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
return integrationManifest;
} else {
return core;
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
}
}
}

View File

@@ -45,6 +45,7 @@ class GitHubRequest {
private final String apiUrl;
private final String urlPath;
private final String method;
private final RateLimitTarget rateLimitTarget;
private final InputStream body;
private final boolean forceBody;
@@ -56,6 +57,7 @@ class GitHubRequest {
@Nonnull String apiUrl,
@Nonnull String urlPath,
@Nonnull String method,
@Nonnull RateLimitTarget rateLimitTarget,
@CheckForNull InputStream body,
boolean forceBody) throws MalformedURLException {
this.args = Collections.unmodifiableList(new ArrayList<>(args));
@@ -64,6 +66,7 @@ class GitHubRequest {
this.apiUrl = apiUrl;
this.urlPath = urlPath;
this.method = method;
this.rateLimitTarget = rateLimitTarget;
this.body = body;
this.forceBody = forceBody;
String tailApiUrl = buildTailApiUrl();
@@ -119,6 +122,16 @@ class GitHubRequest {
return method;
}
/**
* The rate limit target for this request.
*
* @return the rate limit to use for this request.
*/
@Nonnull
public RateLimitTarget rateLimitTarget() {
return rateLimitTarget;
}
/**
* The arguments for this request. Depending on the {@link #method()} and {@code #inBody()} these maybe added to the
* url or to the request body.
@@ -217,7 +230,15 @@ class GitHubRequest {
* @return a {@link Builder} based on this request.
*/
public Builder<?> toBuilder() {
return new Builder<>(args, headers, injectedMappingValues, apiUrl, urlPath, method, body, forceBody);
return new Builder<>(args,
headers,
injectedMappingValues,
apiUrl,
urlPath,
method,
rateLimitTarget,
body,
forceBody);
}
private String buildTailApiUrl() {
@@ -281,6 +302,10 @@ class GitHubRequest {
*/
@Nonnull
private String method;
@Nonnull
private RateLimitTarget rateLimitTarget;
private InputStream body;
private boolean forceBody;
@@ -294,6 +319,7 @@ class GitHubRequest {
GitHubClient.GITHUB_URL,
"/",
"GET",
RateLimitTarget.CORE,
null,
false);
}
@@ -304,6 +330,7 @@ class GitHubRequest {
@Nonnull String apiUrl,
@Nonnull String urlPath,
@Nonnull String method,
@Nonnull RateLimitTarget rateLimitTarget,
@CheckForNull @WillClose InputStream body,
boolean forceBody) {
this.args = new ArrayList<>(args);
@@ -312,6 +339,7 @@ class GitHubRequest {
this.apiUrl = apiUrl;
this.urlPath = urlPath;
this.method = method;
this.rateLimitTarget = rateLimitTarget;
this.body = body;
this.forceBody = forceBody;
}
@@ -324,7 +352,15 @@ class GitHubRequest {
* if the GitHub API URL cannot be constructed
*/
public GitHubRequest build() throws MalformedURLException {
return new GitHubRequest(args, headers, injectedMappingValues, apiUrl, urlPath, method, body, forceBody);
return new GitHubRequest(args,
headers,
injectedMappingValues,
apiUrl,
urlPath,
method,
rateLimitTarget,
body,
forceBody);
}
/**
@@ -562,6 +598,18 @@ class GitHubRequest {
return (B) this;
}
/**
* Method requester.
*
* @param rateLimitTarget
* the rate limit target for this request. Default is {@link RateLimitTarget#CORE}.
* @return the request builder
*/
public B rateLimit(@Nonnull RateLimitTarget rateLimitTarget) {
this.rateLimitTarget = rateLimitTarget;
return (B) this;
}
/**
* Content type requester.
*

View File

@@ -5,20 +5,17 @@ import java.util.logging.Logger;
/**
* A GitHub API Rate Limit Checker called before each request
*
* <p>
* GitHub allots a certain number of requests to each user or application per period of time (usually per hour). The
* number of requests remaining is returned in the response header and can also be requested using
* {@link GitHub#getRateLimit()}. This requests per interval is referred to as the "rate limit".
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
* remaining and the time when the number will be reset is returned in the response header and can also be requested
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
* </p>
* <p>
* GitHub prefers that clients stop before exceeding their rate limit rather than stopping after they exceed it. The
* {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are
* met.
* </p>
* <p>
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit, but each {@link GitHub}
* instance will attempt to cache and reuse the last see rate limit rather than making a new request.
* </p>
*/
public abstract class RateLimitChecker {
@@ -33,28 +30,21 @@ public abstract class RateLimitChecker {
* free to choose whatever strategy they prefer for what is considered to exceed the budget and how long to sleep.
*
* <p>
* The caller of this method figures out which {@link GHRateLimit.Record} applies for the current request add
* The caller of this method figures out which {@link GHRateLimit.Record} applies for the current request and
* provides it to this method.
* </p>
* <p>
* It is important to remember that rate limit reset times are only accurate to the second. Trying to sleep to
* exactly the reset time would be likely to produce worse behavior rather than better. For this reason
* {@link GitHubRateLimitChecker} may choose to add more sleep times when a checker indicates the rate limit was
* exceeded.
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will retrieve
* updated rate limit information and call this method again with {@code count} incremented by one. When this
* checker returns {@code false}, the calling {@link GitHubRateLimitChecker} will let the request continue.
* </p>
* <p>
* As long as this method returns {@code true} it is guaranteed that {@link GitHubRateLimitChecker} will get updated
* rate limit information and call this method again with {@code count} incremented by one. After this method
* returns {@code true} at least once, the calling {@link GitHubRateLimitChecker} may choose to wait some additional
* period of time between calls to this checker.
* Rate limit reset times are only accurate to the second. Trying to sleep to exactly the reset time could result in
* requests being sent before the new rate limit was available. For this reason, if this method returned
* {@code true} at least once for a particular request, {@link GitHubRateLimitChecker} may choose to sleep for some
* small additional between calls and before letting the request continue.
* </p>
* <p>
* After this checker returns {@code false}, the calling {@link GitHubRateLimitChecker} will let the request
* continue. If this method returned {@code true} at least once for a particular request, the calling
* {@link GitHubRateLimitChecker} may choose to wait some additional period of time before letting the request be
* sent.
* </p>
*
*
* @param rateLimitRecord
* the current {@link GHRateLimit.Record} to check against.
* @param count

View File

@@ -0,0 +1,36 @@
package org.kohsuke.github;
/**
* Specifies the rate limit record of an operation.
*
* @see GitHubBuilder#withRateLimitChecker(RateLimitChecker, RateLimitTarget)
*/
public enum RateLimitTarget {
/**
* Selects or updates the {@link GHRateLimit#getCore()} record.
*/
CORE,
/**
* Selects or updates the {@link GHRateLimit#getSearch()} record.
*/
SEARCH,
/**
* Selects or updates the {@link GHRateLimit#getGraphQL()} record.
*/
GRAPHQL,
/**
* Selects or updates the {@link GHRateLimit#getIntegrationManifest()} record.
*/
INTEGRATION_MANIFEST,
/**
* Selects no rate limit.
*
* This request uses no rate limit. If the response header includes rate limit information, it will apply to
* {@link #CORE}.
*/
NONE
}

View File

@@ -0,0 +1,31 @@
Creating resources at the organization level
In order to create resources in GitHub for a given organization, you must first create an object of type <<<GHOrganization>>>.
As an example:
+-----+
GHOrganization organizationClient(GitHub gitHub, String organizationName) throws IOException {
return gitHub.getOrganization(organizationName);
}
+-----+
Now you can easily work with the GHOrganization, and all methods that will create resources will create them
in the given organization.
One of the most common use cases is to create a repository in a given organization:
+-----+
void createRepository(GHOrganization organization) {
organization.createRepository("repository-name")
.private_(true)
.wiki(false)
.projects(false)
.description("Description")
.allowMergeCommit(true)
.allowSquashMerge(false)
.allowRebaseMerge(false)
.create()
}
+-----+

View File

@@ -23,6 +23,7 @@
<item name="JWT Authentication" href="/githubappjwtauth.html"/>
<item name="App Installation Token " href="/githubappappinsttokenauth.html"/>
</item>
<item name="Working with organizations" href="/createorglevelresources.html"/>
</menu>
<menu name="References">

View File

@@ -20,6 +20,7 @@ import java.util.Map.Entry;
import java.util.regex.Pattern;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.oneOf;
@@ -108,7 +109,7 @@ public class AppTest extends AbstractGitHubWireMockTest {
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
.withEndpoint(mockGitHub.apiServer().baseUrl())
.build();
assertThat(gitHub.lastRateLimit(), nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
assertFalse(gitHub.isCredentialValid());
// For invalid credentials, we get a 401 but it includes anonymous rate limit headers
assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class)));
@@ -118,18 +119,22 @@ public class AppTest extends AbstractGitHubWireMockTest {
@Test
public void testCredentialValidEnterprise() throws IOException {
// Simulated GHE: getRateLimit returns 404
assertThat(gitHub.lastRateLimit(), nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(true));
assertTrue(gitHub.isCredentialValid());
// lastRateLimit stays null when 404 is encountered
assertThat(gitHub.lastRateLimit(), nullValue());
// lastRateLimitUpdates because 404 still includes header rate limit info
assertThat(gitHub.lastRateLimit(), notNullValue());
assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT)));
assertThat(gitHub.lastRateLimit().getCore().isExpired(), is(false));
gitHub = getGitHubBuilder().withOAuthToken("bogus", "user")
.withEndpoint(mockGitHub.apiServer().baseUrl())
.build();
assertThat(gitHub.lastRateLimit(), nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
assertFalse(gitHub.isCredentialValid());
// Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info
assertThat(gitHub.lastRateLimit(), nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
}
@Test

View File

@@ -12,6 +12,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.hasProperty;
/**
* Integration test for {@link GHContent}.
@@ -170,4 +171,24 @@ public class GHContentIntegrationTest extends AbstractGitHubWireMockTest {
final GHContent fileContent2 = repo.getFileContent(fileContent.getPath());
assertThat(IOUtils.readLines(fileContent2.read(), StandardCharsets.UTF_8), hasItems("test"));
}
@Test
public void testGetFileContentWithSymlink() throws Exception {
final GHRepository repo = gitHub.getRepository("hub4j-test-org/GHContentIntegrationTest");
final GHContent fileContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-file");
// for whatever reason GH says this is a file :-o
assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is("thanks for reading me\n"));
final GHContent dirContent = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir");
// but symlinks to directories are symlinks!
assertThat(dirContent,
allOf(hasProperty("target", is("a-dir-with-3-entries")), hasProperty("type", is("symlink"))));
// future somehow...
// final GHContent fileContent2 = repo.getFileContent("ghcontent-ro/a-symlink-to-a-dir/entry-one");
// this needs special handling and will 404 from GitHub
// assertThat(IOUtils.toString(fileContent.read(), StandardCharsets.UTF_8), is(""));
}
}

View File

@@ -3,13 +3,15 @@ package org.kohsuke.github;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import java.io.IOException;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
@@ -51,13 +53,16 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
public void testGitHubRateLimit() throws Exception {
// Customized response that templates the date to keep things working
snapshotNotAllowed();
GHRateLimit.UnknownLimitRecord.reset();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
// 4897 is just the what the limit was when the snapshot was taken
previousLimit = GHRateLimit.fromHeaderRecord(new GHRateLimit.Record(5000,
4897,
(templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L));
previousLimit = GHRateLimit.fromRecord(
new GHRateLimit.Record(5000,
4897,
(templating.testStartDate.getTime() + Duration.ofHours(1).toMillis()) / 1000L),
RateLimitTarget.CORE);
// -------------------------------------------------------------
// /user gets response with rate limit information
@@ -76,8 +81,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
// Give this a moment
Thread.sleep(1500);
// ratelimit() uses headerRateLimit if available and headerRateLimit is not expired
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
// ratelimit() uses cached rate limit if available and not expired
assertThat(gitHub.rateLimit(), sameInstance(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(1));
@@ -88,8 +93,13 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
// Because remaining and reset date are unchanged, the header should be unchanged as well
assertThat(gitHub.lastRateLimit(), sameInstance(headerRateLimit));
// Because remaining and reset date are unchanged in core, the header should be unchanged as well
// But the overall instance has changed because of filling in of unknown data.
assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit)));
// Identical Records should be preserved even when GHRateLimit is merged
assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore()));
assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch())));
headerRateLimit = gitHub.lastRateLimit();
// rate limit request is free, remaining is unchanged
verifyRateLimitValues(previousLimit, previousLimit.getRemaining());
@@ -141,9 +151,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
verifyRateLimitValues(previousLimit, previousLimit.getRemaining(), true);
previousLimit = rateLimit;
// When getRateLimit() succeeds, headerRateLimit updates as usual as well (if needed)
// These are separate instances, but should be equal
assertThat(gitHub.rateLimit(), not(sameInstance(rateLimit)));
// When getRateLimit() succeeds, cached rate limit updates as usual as well (if needed)
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
// Verify different record instances can be compared
assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore()));
@@ -154,8 +163,43 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit()));
headerRateLimit = gitHub.lastRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(5));
// Verify the requesting a search url updates the search rate limit
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(30));
HashMap<String, Object> searchResult = (HashMap<String, Object>) gitHub.createRequest()
.rateLimit(RateLimitTarget.SEARCH)
.setRawUrlPath(mockGitHub.apiServer().baseUrl()
+ "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc")
.fetch(HashMap.class);
assertThat(searchResult.get("total_count"), equalTo(1918));
assertThat(mockGitHub.getRequestCount(), equalTo(6));
assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore()));
assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch())));
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(29));
PagedSearchIterable<GHRepository> searchResult2 = gitHub.searchRepositories()
.q("tetris")
.language("assembly")
.sort(GHRepositorySearchBuilder.Sort.STARS)
.order(GHDirection.DESC)
.list();
assertThat(searchResult2.getTotalCount(), equalTo(1918));
assertThat(mockGitHub.getRequestCount(), equalTo(7));
assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore()));
assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch())));
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(28));
}
private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) {
@@ -194,6 +238,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception {
// Customized response that results in file not found the same as GitHub Enterprise
snapshotNotAllowed();
GHRateLimit.UnknownLimitRecord.reset();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
@@ -203,13 +249,14 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
Thread.sleep(1500);
// -------------------------------------------------------------
// Before any queries, rate limit starts as null but may be requested
// Before any queries, rate limit starts as default but may be requested
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
assertThat(mockGitHub.getRequestCount(), equalTo(0));
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
rateLimit = gitHub.rateLimit();
assertThat(gitHub.lastRateLimit(), not(equalTo(GHRateLimit.DEFAULT)));
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
@@ -218,8 +265,8 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// last is still null, because it actually means lastHeaderRateLimit
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
// lastRateLimit the same as rateLimit
assertThat(gitHub.lastRateLimit(), sameInstance(rateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(1));
@@ -233,13 +280,14 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
gitHub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
rateLimit = gitHub.rateLimit();
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// Same unknown instance is reused for a while
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
lastReset = rateLimit.getResetDate();
assertThat(mockGitHub.getRequestCount(), equalTo(3));
@@ -258,17 +306,19 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// When not expired, unknowns do not replace each other so last reset remains unchanged
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
// Give this a moment
Thread.sleep(1500);
// last is still null, because it actually means lastHeaderRateLimit
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
// lastRateLimit the same as rateLimit
assertThat(gitHub.lastRateLimit(), sameInstance(rateLimit));
// ratelimit() tries not to make additional requests, uses queried rate limit since header not available
Thread.sleep(1500);
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(4));
// -------------------------------------------------------------
// Some versions of GHE include header rate limit information, some do not
@@ -282,10 +332,12 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.getLimit(), equalTo(5000));
assertThat(rateLimit.getRemaining(), equalTo(4978));
// The previous record was an "Unknown", so even though this records resets sooner we take it
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(-1));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
lastReset = rateLimit.getResetDate();
// When getting only header updates, the unknowns are also expired
assertThat(rateLimit.getSearch().isExpired(), is(true));
GHRateLimit headerRateLimit = rateLimit;
// Give this a moment
@@ -320,14 +372,49 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
// getRateLimit() uses headerRateLimit if /rate_limit returns a 404
// and headerRateLimit is available and not expired
assertThat(rateLimit, sameInstance(gitHub.lastRateLimit()));
headerRateLimit = rateLimit;
// ratelimit() should prefer headerRateLimit when getRateLimit fails and headerRateLimit is not expired
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(6));
// Wait for the header
Thread.sleep(1500);
// Verify the requesting a search url updates the search rate limit
// Core rate limit record should not change while search is updated.
assertThat(gitHub.lastRateLimit().getSearch(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(gitHub.lastRateLimit().getSearch().isExpired(), equalTo(true));
HashMap<String, Object> searchResult = (HashMap<String, Object>) gitHub.createRequest()
.rateLimit(RateLimitTarget.SEARCH)
.setRawUrlPath(mockGitHub.apiServer().baseUrl()
+ "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc")
.fetch(Object.class);
assertThat(searchResult.get("total_count"), equalTo(1918));
assertThat(mockGitHub.getRequestCount(), equalTo(7));
assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore()));
assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch())));
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(29));
PagedSearchIterable<GHRepository> searchResult2 = gitHub.searchRepositories()
.q("tetris")
.language("assembly")
.sort(GHRepositorySearchBuilder.Sort.STARS)
.order(GHDirection.DESC)
.list();
assertThat(searchResult2.getTotalCount(), equalTo(1918));
assertThat(mockGitHub.getRequestCount(), equalTo(8));
assertThat(gitHub.lastRateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.lastRateLimit().getCore(), sameInstance(headerRateLimit.getCore()));
assertThat(gitHub.lastRateLimit().getSearch(), not(sameInstance(headerRateLimit.getSearch())));
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(28));
}
@Test
@@ -372,6 +459,7 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
private void executeExpirationTest() throws Exception {
// Customized response that templates the date to keep things working
snapshotNotAllowed();
GHRateLimit.UnknownLimitRecord.reset();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
@@ -413,15 +501,15 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat("Header instance has expired", gitHub.lastRateLimit().isExpired(), is(true));
assertThat("rateLimit() will ask server when header instance expires and it has not called getRateLimit() yet",
assertThat("rateLimit() will ask server when cached instance has expired",
gitHub.rateLimit(),
not(sameInstance(rateLimit)));
assertThat("lastRateLimit() (header instance) is populated as part of internal call to getRateLimit()",
assertThat("lastRateLimit() is populated as part of internal call to getRateLimit()",
gitHub.lastRateLimit(),
not(sameInstance(rateLimit)));
assertThat("After request, rateLimit() selects header instance since it has been refreshed",
assertThat("After request, rateLimit() selects cached since it has been refreshed",
gitHub.rateLimit(),
sameInstance(gitHub.lastRateLimit()));
@@ -429,27 +517,27 @@ public class GHRateLimitTest extends AbstractGitHubWireMockTest {
assertThat(mockGitHub.getRequestCount(), equalTo(2));
// This time, rateLimit() should find an expired header record, but a valid returned record
// During the previous call we returned expired header info but valid returned record
// Merging means this has already been merged into a valid cached instance
Thread.sleep(4000);
rateLimit = gitHub.rateLimit();
// Using custom data to have a header instance that expires before the queried instance
assertThat(
"if header instance expires but queried instance is valid, ratelimit() uses it without asking server",
assertThat("if valid ratelimit() uses it without asking server",
gitHub.rateLimit(),
not(sameInstance(gitHub.lastRateLimit())));
sameInstance(gitHub.lastRateLimit()));
assertThat("ratelimit() should almost never return a return a GHRateLimit that is already expired",
gitHub.rateLimit().isExpired(),
is(false));
assertThat("Header instance hasn't been reloaded", gitHub.lastRateLimit(), sameInstance(headerRateLimit));
assertThat("Header instance has expired", gitHub.lastRateLimit().isExpired(), is(true));
assertThat("Cached instance hasn't been reloaded", gitHub.lastRateLimit(), sameInstance(headerRateLimit));
assertThat("Cached instance has not expired", gitHub.lastRateLimit().isExpired(), is(false));
assertThat(mockGitHub.getRequestCount(), equalTo(2));
// Finally they both expire and rateLimit() should find both expired and get a new record
// Finally the cached instance expires and rateLimit() should get a new record
Thread.sleep(2000);
headerRateLimit = gitHub.rateLimit();

View File

@@ -0,0 +1,32 @@
package org.kohsuke.github;
import org.junit.Test;
import java.io.IOException;
public class GHTeamBuilderTest extends AbstractGitHubWireMockTest {
@Test
public void testCreateChildTeam() throws IOException {
String parentTeamSlug = "dummy-team";
String childTeamSlug = "dummy-team-child";
String description = "description";
// Get the parent team
GHTeam parentTeam = gitHub.getOrganization(GITHUB_API_TEST_ORG).getTeamBySlug(parentTeamSlug);
// Create a child team, using the parent team identifier
GHTeam childTeam = gitHub.getOrganization(GITHUB_API_TEST_ORG)
.createTeam(childTeamSlug)
.description(description)
.privacy(GHTeam.Privacy.CLOSED)
.parentTeamId(parentTeam.getId())
.create();
assertEquals(description, childTeam.getDescription());
assertEquals(childTeamSlug, childTeam.getName());
assertEquals(childTeamSlug, childTeam.getSlug());
assertEquals(GHTeam.Privacy.CLOSED, childTeam.getPrivacy());
}
}

View File

@@ -4,6 +4,7 @@ import org.junit.Test;
import org.kohsuke.github.GHTeam.Privacy;
import java.io.IOException;
import java.util.Set;
import static org.junit.Assert.assertEquals;
@@ -56,4 +57,27 @@ public class GHTeamTest extends AbstractGitHubWireMockTest {
assertEquals(privacy, team.getPrivacy());
}
@Test
public void testFetchChildTeams() throws IOException {
String teamSlug = "dummy-team";
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
GHTeam team = org.getTeamBySlug(teamSlug);
Set<GHTeam> result = team.listChildTeams().toSet();
assertEquals(1, result.size());
assertEquals("child-team-for-dummy", result.toArray(new GHTeam[]{})[0].getName());
}
@Test
public void testFetchEmptyChildTeams() throws IOException {
String teamSlug = "simple-team";
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
GHTeam team = org.getTeamBySlug(teamSlug);
Set<GHTeam> result = team.listChildTeams().toSet();
assertEquals(0, result.size());
}
}

View File

@@ -9,7 +9,7 @@ import java.util.Date;
import java.util.TimeZone;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.fail;
/**
* Unit test for {@link GitHub} static helpers.
@@ -65,68 +65,137 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest {
@Test
public void testGitHubRateLimitShouldReplaceRateLimit() throws Exception {
GHRateLimit.Record unknown0 = GHRateLimit.Unknown().getCore();
GHRateLimit.Record unknown1 = GHRateLimit.Unknown().getCore();
GHRateLimit.UnknownLimitRecord.reset();
GHRateLimit.UnknownLimitRecord.unknownLimitResetSeconds = 5;
GHRateLimit.Record record0 = new GHRateLimit.Record(10, 10, 10L);
GHRateLimit.Record record1 = new GHRateLimit.Record(10, 9, 10L);
GHRateLimit.Record record2 = new GHRateLimit.Record(10, 2, 10L);
GHRateLimit.Record record3 = new GHRateLimit.Record(10, 10, 20L);
GHRateLimit.Record record4 = new GHRateLimit.Record(10, 5, 20L);
GHRateLimit.Record unknown0 = GHRateLimit.UnknownLimitRecord.current();
Thread.sleep(2000);
Thread.sleep(1500);
GHRateLimit.UnknownLimitRecord.reset();
GHRateLimit.UnknownLimitRecord.unknownLimitResetSeconds = 5;
// For testing, we create an new unknown.
GHRateLimit.Record unknown1 = GHRateLimit.UnknownLimitRecord.current();
assertThat("Valid unknown should not replace an existing one, regardless of created or reset time",
unknown1.currentOrUpdated(unknown0),
sameInstance(unknown1));
assertThat("Valid unknown should not replace an existing one, regardless of created or reset time",
unknown0.currentOrUpdated(unknown1),
sameInstance(unknown0));
// Sleep to make different created time
Thread.sleep(1500);
// To reduce object creation: There is only one valid Unknown record at a time.
assertThat("Unknown current should should limit the creation of new unknown records",
unknown1,
sameInstance(GHRateLimit.UnknownLimitRecord.current()));
long epochSeconds = Instant.now().getEpochSecond();
GHRateLimit.Record record0 = new GHRateLimit.Record(10, 10, epochSeconds + 10L);
GHRateLimit.Record record1 = new GHRateLimit.Record(10, 9, epochSeconds + 10L);
GHRateLimit.Record record2 = new GHRateLimit.Record(10, 2, epochSeconds + 10L);
GHRateLimit.Record record3 = new GHRateLimit.Record(10, 10, epochSeconds + 20L);
GHRateLimit.Record record4 = new GHRateLimit.Record(10, 5, epochSeconds + 20L);
GHRateLimit.Record recordExpired0 = new GHRateLimit.Record(10, 10, epochSeconds - 1L);
GHRateLimit.Record recordExpired1 = new GHRateLimit.Record(10, 10, epochSeconds + 2L);
// Sleep to make expired and different created time
Thread.sleep(4000);
GHRateLimit.Record recordWorst = new GHRateLimit.Record(Integer.MAX_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE);
GHRateLimit.Record record00 = new GHRateLimit.Record(10, 10, 10L);
GHRateLimit.Record unknown2 = GHRateLimit.Unknown().getCore();
GHRateLimit.Record record00 = new GHRateLimit.Record(10, 10, epochSeconds + 10L);
GHRateLimit.Record unknownExpired0 = unknown0;
GHRateLimit.Record unknownExpired1 = unknown1;
unknown0 = GHRateLimit.UnknownLimitRecord.current();
// Rate-limit records maybe created and returned in different orders.
// We should update to the regular records over unknowns.
// We should update to the unexpired regular records over unknowns.
// After that, we should update to the candidate if its limit is lower or its reset is later.
assertThat("Equivalent unknown should not replace", GitHubClient.shouldReplace(unknown0, unknown1), is(false));
assertThat("Equivalent unknown should not replace", GitHubClient.shouldReplace(unknown1, unknown0), is(false));
assertThat("Expired unknowns should not replace another expired one, regardless of created or reset time",
unknownExpired0.currentOrUpdated(unknownExpired1),
sameInstance(unknownExpired0));
assertThat("Expired unknowns should not replace another expired one, regardless of created or reset time",
unknownExpired1.currentOrUpdated(unknownExpired0),
sameInstance(unknownExpired1));
assertThat("Later unknown should replace earlier", GitHubClient.shouldReplace(unknown2, unknown0), is(true));
assertThat("Earlier unknown should not replace later",
GitHubClient.shouldReplace(unknown0, unknown2),
is(false));
assertThat("Expired unknown should not be replaced by expired earlier normal record",
unknownExpired0.currentOrUpdated(recordExpired0),
sameInstance(unknownExpired0));
assertThat("Expired normal record should not be replaced an expired earlier unknown record",
recordExpired0.currentOrUpdated(unknownExpired0),
sameInstance(recordExpired0));
assertThat("Worst record should replace later unknown",
GitHubClient.shouldReplace(recordWorst, unknown1),
is(true));
assertThat("Unknown should not replace worst record",
GitHubClient.shouldReplace(unknown1, recordWorst),
is(false));
assertThat("Expired unknown should be replaced by expired later normal record",
unknownExpired0.currentOrUpdated(recordExpired1),
sameInstance(recordExpired1));
assertThat(
"Expired later normal record should not be replaced an expired unknown record, regardless of created or reset time",
recordExpired1.currentOrUpdated(unknownExpired0),
sameInstance(recordExpired1));
assertThat("Earlier record should replace later worst",
GitHubClient.shouldReplace(record0, recordWorst),
is(true));
assertThat("Valid unknown should not be replaced by an expired unknown",
unknown0.currentOrUpdated(unknownExpired0),
sameInstance(unknown0));
assertThat("Expired unknown should be replaced by valid unknown",
unknownExpired0.currentOrUpdated(unknown0),
sameInstance(unknown0));
assertThat("Valid unknown should replace an expired normal record",
recordExpired1.currentOrUpdated(unknown0),
sameInstance(unknown0));
assertThat("Valid unknown record should not be replaced by expired normal record",
unknown0.currentOrUpdated(recordExpired1),
sameInstance(unknown0));
// In normal comparision, expiration doesn't matter
assertThat("Expired normal should not be replaced by an earlier expired one",
recordExpired1.currentOrUpdated(recordExpired0),
sameInstance(recordExpired1));
assertThat("Expired normal should be replaced by a later expired one",
recordExpired0.currentOrUpdated(recordExpired1),
sameInstance(recordExpired1));
assertThat("Later worst record should be replaced by earlier record",
recordWorst.currentOrUpdated(record0),
sameInstance(record0));
assertThat("Later worst record should not replace earlier",
GitHubClient.shouldReplace(recordWorst, record0),
is(false));
record0.currentOrUpdated(recordWorst),
sameInstance(record0));
assertThat("Equivalent record should not replace", GitHubClient.shouldReplace(record0, record00), is(false));
assertThat("Equivalent record should not replace", GitHubClient.shouldReplace(record00, record0), is(false));
assertThat("Equivalent record should not replace other",
record00.currentOrUpdated(record0),
sameInstance(record00));
assertThat("Equivalent record should not replace other",
record0.currentOrUpdated(record00),
sameInstance(record0));
assertThat("Lower limit record should replace higher", GitHubClient.shouldReplace(record1, record0), is(true));
assertThat("Lower limit record should replace higher", GitHubClient.shouldReplace(record2, record1), is(true));
assertThat("Higher limit record should be replaced by lower",
record0.currentOrUpdated(record1),
sameInstance(record1));
assertThat("Higher limit record should be replaced by lower",
record1.currentOrUpdated(record2),
sameInstance(record2));
assertThat("Higher limit record should not replace lower",
GitHubClient.shouldReplace(record1, record2),
is(false));
assertThat("Lower limit record should not be replaced higher",
record2.currentOrUpdated(record1),
sameInstance(record2));
assertThat("Higher limit record with later reset should replace lower",
GitHubClient.shouldReplace(record3, record2),
is(true));
assertThat("Lower limit record should be replaced by higher limit record with later reset",
record2.currentOrUpdated(record3),
sameInstance(record3));
assertThat("Lower limit record with later reset should replace higher",
GitHubClient.shouldReplace(record4, record1),
is(true));
assertThat("Higher limit record should be replaced by lower limit record with later reset",
record1.currentOrUpdated(record4),
sameInstance(record4));
assertThat("Lower limit record with earlier reset should not replace higher",
GitHubClient.shouldReplace(record2, record4),
is(false));
assertThat("Higher limit record should not be replaced by lower limit record with earlier reset",
record4.currentOrUpdated(record2),
sameInstance(record4));
}

View File

@@ -32,6 +32,7 @@ public class RateLimitCheckerTest extends AbstractGitHubWireMockTest {
public void testGitHubRateLimit() throws Exception {
// Customized response that templates the date to keep things working
snapshotNotAllowed();
GHRateLimit.UnknownLimitRecord.reset();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
@@ -45,7 +46,7 @@ public class RateLimitCheckerTest extends AbstractGitHubWireMockTest {
.withEndpoint(mockGitHub.apiServer().baseUrl())
.build();
assertThat(gitHub.lastRateLimit(), nullValue());
assertThat(gitHub.lastRateLimit(), sameInstance(GHRateLimit.DEFAULT));
// Checks the rate limit before getting myself
gitHub.getMyself();

View File

@@ -2,6 +2,7 @@ package org.kohsuke.github.junit;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.admin.model.*;
import com.github.tomakehurst.wiremock.client.CountMatchingStrategy;
import com.github.tomakehurst.wiremock.client.MappingBuilder;
import com.github.tomakehurst.wiremock.client.VerificationException;
import com.github.tomakehurst.wiremock.client.WireMock;
@@ -221,6 +222,10 @@ public class WireMockRule implements MethodRule, TestRule, Container, Stubbing,
wireMockServer.verify(count, requestPatternBuilder);
}
public void verify(CountMatchingStrategy countMatchingStrategy, RequestPatternBuilder requestPatternBuilder) {
wireMockServer.verify(countMatchingStrategy, requestPatternBuilder);
}
public List<LoggedRequest> findAll(RequestPatternBuilder requestPatternBuilder) {
return wireMockServer.findAll(requestPatternBuilder);
}

View File

@@ -109,6 +109,10 @@ public class WireMockRuleConfiguration implements Options {
return parent.browserProxyingEnabled();
}
public BrowserProxySettings browserProxySettings() {
return parent.browserProxySettings();
}
public ProxySettings proxyVia() {
return parent.proxyVia();
}
@@ -180,4 +184,8 @@ public class WireMockRuleConfiguration implements Options {
public boolean getStubRequestLoggingDisabled() {
return parent.getStubRequestLoggingDisabled();
}
public boolean getStubCorsEnabled() {
return parent.getStubCorsEnabled();
}
}

View File

@@ -0,0 +1,305 @@
{
"id": 40763577,
"node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==",
"name": "GHContentIntegrationTest",
"full_name": "hub4j-test-org/GHContentIntegrationTest",
"private": false,
"owner": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/hub4j-test-org",
"html_url": "https://github.com/hub4j-test-org",
"followers_url": "https://api.github.com/users/hub4j-test-org/followers",
"following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/hub4j-test-org/orgs",
"repos_url": "https://api.github.com/users/hub4j-test-org/repos",
"events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/hub4j-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest",
"description": "Repository used for integration test of github-api",
"fork": true,
"url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest",
"forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks",
"keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams",
"hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks",
"issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}",
"events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events",
"assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}",
"branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}",
"tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags",
"blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}",
"languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages",
"stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers",
"contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors",
"subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers",
"subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription",
"commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}",
"compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges",
"archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads",
"issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}",
"pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}",
"milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}",
"notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}",
"releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}",
"deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments",
"created_at": "2015-08-15T14:14:57Z",
"updated_at": "2020-07-02T15:49:49Z",
"pushed_at": "2020-07-02T15:49:47Z",
"git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git",
"ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git",
"clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git",
"svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest",
"homepage": null,
"size": 54,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 41,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": null,
"forks": 41,
"open_issues": 0,
"watchers": 0,
"default_branch": "master",
"temp_clone_token": null,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/hub4j-test-org",
"html_url": "https://github.com/hub4j-test-org",
"followers_url": "https://api.github.com/users/hub4j-test-org/followers",
"following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/hub4j-test-org/orgs",
"repos_url": "https://api.github.com/users/hub4j-test-org/repos",
"events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/hub4j-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"parent": {
"id": 19653852,
"node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==",
"name": "GHContentIntegrationTest",
"full_name": "kohsuke2/GHContentIntegrationTest",
"private": false,
"owner": {
"login": "kohsuke2",
"id": 1329242,
"node_id": "MDQ6VXNlcjEzMjkyNDI=",
"avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/kohsuke2",
"html_url": "https://github.com/kohsuke2",
"followers_url": "https://api.github.com/users/kohsuke2/followers",
"following_url": "https://api.github.com/users/kohsuke2/following{/other_user}",
"gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}",
"starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions",
"organizations_url": "https://api.github.com/users/kohsuke2/orgs",
"repos_url": "https://api.github.com/users/kohsuke2/repos",
"events_url": "https://api.github.com/users/kohsuke2/events{/privacy}",
"received_events_url": "https://api.github.com/users/kohsuke2/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/kohsuke2/GHContentIntegrationTest",
"description": "Repository used for integration test of github-api",
"fork": true,
"url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest",
"forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks",
"keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams",
"hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks",
"issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}",
"events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events",
"assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}",
"branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}",
"tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags",
"blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}",
"languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages",
"stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers",
"contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors",
"subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers",
"subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription",
"commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}",
"compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges",
"archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads",
"issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}",
"pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}",
"milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}",
"notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}",
"releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}",
"deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments",
"created_at": "2014-05-10T22:50:30Z",
"updated_at": "2018-11-07T15:36:19Z",
"pushed_at": "2018-11-07T15:36:18Z",
"git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git",
"ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git",
"clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git",
"svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest",
"homepage": null,
"size": 111,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"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": 0,
"license": null,
"forks": 1,
"open_issues": 0,
"watchers": 0,
"default_branch": "master"
},
"source": {
"id": 14779458,
"node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==",
"name": "github-api-test-1",
"full_name": "farmdawgnation/github-api-test-1",
"private": false,
"owner": {
"login": "farmdawgnation",
"id": 620189,
"node_id": "MDQ6VXNlcjYyMDE4OQ==",
"avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/farmdawgnation",
"html_url": "https://github.com/farmdawgnation",
"followers_url": "https://api.github.com/users/farmdawgnation/followers",
"following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}",
"gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}",
"starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions",
"organizations_url": "https://api.github.com/users/farmdawgnation/orgs",
"repos_url": "https://api.github.com/users/farmdawgnation/repos",
"events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}",
"received_events_url": "https://api.github.com/users/farmdawgnation/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/farmdawgnation/github-api-test-1",
"description": "Repository used for integration test of github-api",
"fork": false,
"url": "https://api.github.com/repos/farmdawgnation/github-api-test-1",
"forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks",
"keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams",
"hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks",
"issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}",
"events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events",
"assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}",
"branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}",
"tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags",
"blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}",
"languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages",
"stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers",
"contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors",
"subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers",
"subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription",
"commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}",
"compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges",
"archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads",
"issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}",
"pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}",
"milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}",
"notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}",
"releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}",
"deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments",
"created_at": "2013-11-28T14:46:38Z",
"updated_at": "2016-02-05T13:33:23Z",
"pushed_at": "2013-11-28T14:55:36Z",
"git_url": "git://github.com/farmdawgnation/github-api-test-1.git",
"ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git",
"clone_url": "https://github.com/farmdawgnation/github-api-test-1.git",
"svn_url": "https://github.com/farmdawgnation/github-api-test-1",
"homepage": null,
"size": 89,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 59,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": null,
"forks": 59,
"open_issues": 0,
"watchers": 0,
"default_branch": "master"
},
"network_count": 59,
"subscribers_count": 0
}

View File

@@ -0,0 +1,305 @@
{
"id": 40763577,
"node_id": "MDEwOlJlcG9zaXRvcnk0MDc2MzU3Nw==",
"name": "GHContentIntegrationTest",
"full_name": "hub4j-test-org/GHContentIntegrationTest",
"private": false,
"owner": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/hub4j-test-org",
"html_url": "https://github.com/hub4j-test-org",
"followers_url": "https://api.github.com/users/hub4j-test-org/followers",
"following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/hub4j-test-org/orgs",
"repos_url": "https://api.github.com/users/hub4j-test-org/repos",
"events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/hub4j-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest",
"description": "Repository used for integration test of github-api",
"fork": true,
"url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest",
"forks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/forks",
"keys_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/teams",
"hooks_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/hooks",
"issue_events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/events{/number}",
"events_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/events",
"assignees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/assignees{/user}",
"branches_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/branches{/branch}",
"tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/tags",
"blobs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/statuses/{sha}",
"languages_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/languages",
"stargazers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/stargazers",
"contributors_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contributors",
"subscribers_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscribers",
"subscription_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/subscription",
"commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/{+path}",
"compare_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/merges",
"archive_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/downloads",
"issues_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/issues{/number}",
"pulls_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/pulls{/number}",
"milestones_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/milestones{/number}",
"notifications_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/labels{/name}",
"releases_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/releases{/id}",
"deployments_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/deployments",
"created_at": "2015-08-15T14:14:57Z",
"updated_at": "2020-07-02T15:49:49Z",
"pushed_at": "2020-07-02T15:49:47Z",
"git_url": "git://github.com/hub4j-test-org/GHContentIntegrationTest.git",
"ssh_url": "git@github.com:hub4j-test-org/GHContentIntegrationTest.git",
"clone_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest.git",
"svn_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest",
"homepage": null,
"size": 54,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 41,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": null,
"forks": 41,
"open_issues": 0,
"watchers": 0,
"default_branch": "master",
"temp_clone_token": null,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/hub4j-test-org",
"html_url": "https://github.com/hub4j-test-org",
"followers_url": "https://api.github.com/users/hub4j-test-org/followers",
"following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/hub4j-test-org/orgs",
"repos_url": "https://api.github.com/users/hub4j-test-org/repos",
"events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/hub4j-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"parent": {
"id": 19653852,
"node_id": "MDEwOlJlcG9zaXRvcnkxOTY1Mzg1Mg==",
"name": "GHContentIntegrationTest",
"full_name": "kohsuke2/GHContentIntegrationTest",
"private": false,
"owner": {
"login": "kohsuke2",
"id": 1329242,
"node_id": "MDQ6VXNlcjEzMjkyNDI=",
"avatar_url": "https://avatars2.githubusercontent.com/u/1329242?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/kohsuke2",
"html_url": "https://github.com/kohsuke2",
"followers_url": "https://api.github.com/users/kohsuke2/followers",
"following_url": "https://api.github.com/users/kohsuke2/following{/other_user}",
"gists_url": "https://api.github.com/users/kohsuke2/gists{/gist_id}",
"starred_url": "https://api.github.com/users/kohsuke2/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/kohsuke2/subscriptions",
"organizations_url": "https://api.github.com/users/kohsuke2/orgs",
"repos_url": "https://api.github.com/users/kohsuke2/repos",
"events_url": "https://api.github.com/users/kohsuke2/events{/privacy}",
"received_events_url": "https://api.github.com/users/kohsuke2/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/kohsuke2/GHContentIntegrationTest",
"description": "Repository used for integration test of github-api",
"fork": true,
"url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest",
"forks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/forks",
"keys_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/teams",
"hooks_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/hooks",
"issue_events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/events{/number}",
"events_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/events",
"assignees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/assignees{/user}",
"branches_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/branches{/branch}",
"tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/tags",
"blobs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/statuses/{sha}",
"languages_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/languages",
"stargazers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/stargazers",
"contributors_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contributors",
"subscribers_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscribers",
"subscription_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/subscription",
"commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/contents/{+path}",
"compare_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/merges",
"archive_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/downloads",
"issues_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/issues{/number}",
"pulls_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/pulls{/number}",
"milestones_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/milestones{/number}",
"notifications_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/labels{/name}",
"releases_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/releases{/id}",
"deployments_url": "https://api.github.com/repos/kohsuke2/GHContentIntegrationTest/deployments",
"created_at": "2014-05-10T22:50:30Z",
"updated_at": "2018-11-07T15:36:19Z",
"pushed_at": "2018-11-07T15:36:18Z",
"git_url": "git://github.com/kohsuke2/GHContentIntegrationTest.git",
"ssh_url": "git@github.com:kohsuke2/GHContentIntegrationTest.git",
"clone_url": "https://github.com/kohsuke2/GHContentIntegrationTest.git",
"svn_url": "https://github.com/kohsuke2/GHContentIntegrationTest",
"homepage": null,
"size": 111,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"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": 0,
"license": null,
"forks": 1,
"open_issues": 0,
"watchers": 0,
"default_branch": "master"
},
"source": {
"id": 14779458,
"node_id": "MDEwOlJlcG9zaXRvcnkxNDc3OTQ1OA==",
"name": "github-api-test-1",
"full_name": "farmdawgnation/github-api-test-1",
"private": false,
"owner": {
"login": "farmdawgnation",
"id": 620189,
"node_id": "MDQ6VXNlcjYyMDE4OQ==",
"avatar_url": "https://avatars2.githubusercontent.com/u/620189?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/farmdawgnation",
"html_url": "https://github.com/farmdawgnation",
"followers_url": "https://api.github.com/users/farmdawgnation/followers",
"following_url": "https://api.github.com/users/farmdawgnation/following{/other_user}",
"gists_url": "https://api.github.com/users/farmdawgnation/gists{/gist_id}",
"starred_url": "https://api.github.com/users/farmdawgnation/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/farmdawgnation/subscriptions",
"organizations_url": "https://api.github.com/users/farmdawgnation/orgs",
"repos_url": "https://api.github.com/users/farmdawgnation/repos",
"events_url": "https://api.github.com/users/farmdawgnation/events{/privacy}",
"received_events_url": "https://api.github.com/users/farmdawgnation/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/farmdawgnation/github-api-test-1",
"description": "Repository used for integration test of github-api",
"fork": false,
"url": "https://api.github.com/repos/farmdawgnation/github-api-test-1",
"forks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/forks",
"keys_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/teams",
"hooks_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/hooks",
"issue_events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/events{/number}",
"events_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/events",
"assignees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/assignees{/user}",
"branches_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/branches{/branch}",
"tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/tags",
"blobs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/statuses/{sha}",
"languages_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/languages",
"stargazers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/stargazers",
"contributors_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contributors",
"subscribers_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscribers",
"subscription_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/subscription",
"commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/contents/{+path}",
"compare_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/merges",
"archive_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/downloads",
"issues_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/issues{/number}",
"pulls_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/pulls{/number}",
"milestones_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/milestones{/number}",
"notifications_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/labels{/name}",
"releases_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/releases{/id}",
"deployments_url": "https://api.github.com/repos/farmdawgnation/github-api-test-1/deployments",
"created_at": "2013-11-28T14:46:38Z",
"updated_at": "2016-02-05T13:33:23Z",
"pushed_at": "2013-11-28T14:55:36Z",
"git_url": "git://github.com/farmdawgnation/github-api-test-1.git",
"ssh_url": "git@github.com:farmdawgnation/github-api-test-1.git",
"clone_url": "https://github.com/farmdawgnation/github-api-test-1.git",
"svn_url": "https://github.com/farmdawgnation/github-api-test-1",
"homepage": null,
"size": 89,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": false,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 59,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": null,
"forks": 59,
"open_issues": 0,
"watchers": 0,
"default_branch": "master"
},
"network_count": 59,
"subscribers_count": 0
}

View File

@@ -0,0 +1,17 @@
{
"name": "a-symlink-to-a-dir",
"path": "ghcontent-ro/a-symlink-to-a-dir",
"sha": "fba4aa592c12413933e5583d5ac0bdfd3ed4eb73",
"size": 20,
"url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-dir?ref=master",
"html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/master/ghcontent-ro/a-symlink-to-a-dir",
"git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/fba4aa592c12413933e5583d5ac0bdfd3ed4eb73",
"download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/master/ghcontent-ro/a-symlink-to-a-dir",
"type": "symlink",
"target": "a-dir-with-3-entries",
"_links": {
"self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-dir?ref=master",
"git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/fba4aa592c12413933e5583d5ac0bdfd3ed4eb73",
"html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/master/ghcontent-ro/a-symlink-to-a-dir"
}
}

View File

@@ -0,0 +1,18 @@
{
"name": "a-symlink-to-a-file",
"path": "ghcontent-ro/a-symlink-to-a-file",
"sha": "dbb84a81d2b1b5eb4077c9b72b7497eb16ed9bf6",
"size": 22,
"url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-file?ref=master",
"html_url": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/master/ghcontent-ro/a-symlink-to-a-file",
"git_url": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/dbb84a81d2b1b5eb4077c9b72b7497eb16ed9bf6",
"download_url": "https://raw.githubusercontent.com/hub4j-test-org/GHContentIntegrationTest/master/ghcontent-ro/a-symlink-to-a-file",
"type": "file",
"content": "dGhhbmtzIGZvciByZWFkaW5nIG1lCg==\n",
"encoding": "base64",
"_links": {
"self": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-file?ref=master",
"git": "https://api.github.com/repos/hub4j-test-org/GHContentIntegrationTest/git/blobs/dbb84a81d2b1b5eb4077c9b72b7497eb16ed9bf6",
"html": "https://github.com/hub4j-test-org/GHContentIntegrationTest/blob/master/ghcontent-ro/a-symlink-to-a-file"
}
}

View File

@@ -0,0 +1,44 @@
{
"id": "054974d2-b150-4d00-9027-c3ad6bbaf023",
"name": "repos_hub4j-test-org_ghcontentintegrationtest",
"request": {
"url": "/repos/hub4j-test-org/GHContentIntegrationTest",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "repos_hub4j-test-org_ghcontentintegrationtest-054974d2-b150-4d00-9027-c3ad6bbaf023.json",
"headers": {
"server": "GitHub.com",
"date": "Thu, 02 Jul 2020 16:17:31 GMT",
"content-type": "application/json; charset=utf-8",
"status": "200 OK",
"cache-control": "public, max-age=60, s-maxage=60",
"vary": "Accept, Accept-Encoding, Accept, X-Requested-With",
"etag": "W/\"8c4b9f7c7b3214f5bd336bb7b8053c36\"",
"last-modified": "Thu, 02 Jul 2020 15:49:49 GMT",
"x-github-media-type": "unknown, github.v3",
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"x-xss-protection": "1; mode=block",
"referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"content-security-policy": "default-src 'none'",
"X-Ratelimit-Limit": "60",
"X-Ratelimit-Remaining": "47",
"X-Ratelimit-Reset": "1593708014",
"Accept-Ranges": "bytes",
"X-GitHub-Request-Id": "EF84:24F2:A8B982:1A6F1B3:5EFE089B"
}
},
"uuid": "054974d2-b150-4d00-9027-c3ad6bbaf023",
"persistent": true,
"scenarioName": "scenario-1-repos-hub4j-test-org-GHContentIntegrationTest",
"requiredScenarioState": "scenario-1-repos-hub4j-test-org-GHContentIntegrationTest-2",
"insertionIndex": 2
}

View File

@@ -0,0 +1,45 @@
{
"id": "81f3af1c-4fb1-4e3b-baa0-c682510d1137",
"name": "repos_hub4j-test-org_ghcontentintegrationtest",
"request": {
"url": "/repos/hub4j-test-org/GHContentIntegrationTest",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "repos_hub4j-test-org_ghcontentintegrationtest-81f3af1c-4fb1-4e3b-baa0-c682510d1137.json",
"headers": {
"server": "GitHub.com",
"date": "Thu, 02 Jul 2020 16:17:31 GMT",
"content-type": "application/json; charset=utf-8",
"status": "200 OK",
"cache-control": "public, max-age=60, s-maxage=60",
"vary": "Accept, Accept-Encoding, Accept, X-Requested-With",
"etag": "W/\"8c4b9f7c7b3214f5bd336bb7b8053c36\"",
"last-modified": "Thu, 02 Jul 2020 15:49:49 GMT",
"x-github-media-type": "unknown, github.v3",
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"x-xss-protection": "1; mode=block",
"referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"content-security-policy": "default-src 'none'",
"X-Ratelimit-Limit": "60",
"X-Ratelimit-Remaining": "48",
"X-Ratelimit-Reset": "1593708014",
"Accept-Ranges": "bytes",
"X-GitHub-Request-Id": "EF84:24F2:A8B94F:1A6F198:5EFE089A"
}
},
"uuid": "81f3af1c-4fb1-4e3b-baa0-c682510d1137",
"persistent": true,
"scenarioName": "scenario-1-repos-hub4j-test-org-GHContentIntegrationTest",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-repos-hub4j-test-org-GHContentIntegrationTest-2",
"insertionIndex": 1
}

View File

@@ -0,0 +1,42 @@
{
"id": "1baf6207-f25f-4064-af9b-8eb1cb1fe125",
"name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-symlink-to-a-dir",
"request": {
"url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-dir",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-symlink-to-a-dir-4.json",
"headers": {
"server": "GitHub.com",
"date": "Thu, 02 Jul 2020 16:17:32 GMT",
"content-type": "application/json; charset=utf-8",
"status": "200 OK",
"cache-control": "public, max-age=60, s-maxage=60",
"vary": "Accept, Accept-Encoding, Accept, X-Requested-With",
"etag": "W/\"fba4aa592c12413933e5583d5ac0bdfd3ed4eb73\"",
"last-modified": "Thu, 02 Jul 2020 15:49:45 GMT",
"x-github-media-type": "unknown, github.v3",
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"x-xss-protection": "1; mode=block",
"referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"content-security-policy": "default-src 'none'",
"X-Ratelimit-Limit": "60",
"X-Ratelimit-Remaining": "45",
"X-Ratelimit-Reset": "1593708014",
"Accept-Ranges": "bytes",
"X-GitHub-Request-Id": "EF84:24F2:A8B9A5:1A6F235:5EFE089B"
}
},
"uuid": "1baf6207-f25f-4064-af9b-8eb1cb1fe125",
"persistent": true,
"insertionIndex": 4
}

View File

@@ -0,0 +1,42 @@
{
"id": "ed777707-b13d-4d06-885f-529154318f3b",
"name": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-symlink-to-a-file",
"request": {
"url": "/repos/hub4j-test-org/GHContentIntegrationTest/contents/ghcontent-ro/a-symlink-to-a-file",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "repos_hub4j-test-org_ghcontentintegrationtest_contents_ghcontent-ro_a-symlink-to-a-file-3.json",
"headers": {
"server": "GitHub.com",
"date": "Thu, 02 Jul 2020 16:17:31 GMT",
"content-type": "application/json; charset=utf-8",
"status": "200 OK",
"cache-control": "public, max-age=60, s-maxage=60",
"vary": "Accept, Accept-Encoding, Accept, X-Requested-With",
"etag": "W/\"dbb84a81d2b1b5eb4077c9b72b7497eb16ed9bf6\"",
"last-modified": "Thu, 02 Jul 2020 15:49:45 GMT",
"x-github-media-type": "unknown, github.v3",
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"x-xss-protection": "1; mode=block",
"referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"content-security-policy": "default-src 'none'",
"X-Ratelimit-Limit": "60",
"X-Ratelimit-Remaining": "46",
"X-Ratelimit-Reset": "1593708014",
"Accept-Ranges": "bytes",
"X-GitHub-Request-Id": "EF84:24F2:A8B991:1A6F20E:5EFE089B"
}
},
"uuid": "ed777707-b13d-4d06-885f-529154318f3b",
"persistent": true,
"insertionIndex": 3
}

View File

@@ -0,0 +1,45 @@
{
"id" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99b",
"name" : "search_repositories",
"request" : {
"url" : "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc",
"method" : "GET",
"headers" : {
"Accept" : {
"equalTo" : "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "search_repositories-6.json",
"headers" : {
"Date" : "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-RateLimit-Limit" : "30",
"X-RateLimit-Remaining" : "29",
"X-RateLimit-Reset" : "{{testStartDate offset='2 minutes' format='unix'}}",
"Cache-Control" : "no-cache",
"X-OAuth-Scopes" : "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes" : "",
"X-GitHub-Media-Type" : "unknown, github.v3",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"Vary" : [ "Accept-Encoding, Accept, X-Requested-With", "Accept-Encoding" ],
"X-GitHub-Request-Id" : "D3E6:7BBB:7F46D5:985A70:5ECDA4F2",
"Link" : "<http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=2>; rel=\"next\", <http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=34>; rel=\"last\""
}
},
"uuid" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99b",
"persistent" : true,
"scenarioName": "scenario-1-search",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-search-2",
"insertionIndex" : 6
}

View File

@@ -0,0 +1,45 @@
{
"id" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99c",
"name" : "search_repositories",
"request" : {
"url" : "/search/repositories?sort=stars&order=desc&q=tetris+language%3Aassembly",
"method" : "GET",
"headers" : {
"Accept" : {
"equalTo" : "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "search_repositories-6.json",
"headers" : {
"Date" : "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-RateLimit-Limit" : "30",
"X-RateLimit-Remaining" : "28",
"X-RateLimit-Reset" : "{{testStartDate offset='2 minutes' format='unix'}}",
"Cache-Control" : "no-cache",
"X-OAuth-Scopes" : "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes" : "",
"X-GitHub-Media-Type" : "unknown, github.v3",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"Vary" : [ "Accept-Encoding, Accept, X-Requested-With", "Accept-Encoding" ],
"X-GitHub-Request-Id" : "D3E6:7BBB:7F46D5:985A70:5ECDA4F2",
"Link" : "<http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=2>; rel=\"next\", <http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=34>; rel=\"last\""
}
},
"uuid" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99c",
"persistent" : true,
"scenarioName": "scenario-1-search",
"requiredScenarioState": "scenario-1-search-2",
"newScenarioState": "scenario-1-search-3",
"insertionIndex" : 7
}

View File

@@ -8,7 +8,7 @@
"search": {
"limit": 30,
"remaining": 30,
"reset": {{testStartDate offset='1 hours' format='unix'}}
"reset": {{testStartDate offset='2 minutes' format='unix'}}
},
"graphql": {
"limit": 5000,

View File

@@ -8,7 +8,7 @@
"search": {
"limit": 30,
"remaining": 30,
"reset": {{testStartDate offset='1 hours' format='unix'}}
"reset": {{testStartDate offset='2 minutes' format='unix'}}
},
"graphql": {
"limit": 5000,

View File

@@ -8,7 +8,7 @@
"search": {
"limit": 30,
"remaining": 30,
"reset": {{testStartDate offset='1 hours' format='unix'}}
"reset": {{testStartDate offset='2 minutes' format='unix'}}
},
"graphql": {
"limit": 5000,

View File

@@ -0,0 +1,45 @@
{
"id" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99b",
"name" : "search_repositories",
"request" : {
"url" : "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc",
"method" : "GET",
"headers" : {
"Accept" : {
"equalTo" : "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "search_repositories-6.json",
"headers" : {
"Date" : "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-RateLimit-Limit" : "30",
"X-RateLimit-Remaining" : "29",
"X-RateLimit-Reset" : "{{testStartDate offset='2 minutes' format='unix'}}",
"Cache-Control" : "no-cache",
"X-OAuth-Scopes" : "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes" : "",
"X-GitHub-Media-Type" : "unknown, github.v3",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"Vary" : [ "Accept-Encoding, Accept, X-Requested-With", "Accept-Encoding" ],
"X-GitHub-Request-Id" : "D3E6:7BBB:7F46D5:985A70:5ECDA4F2",
"Link" : "<http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=2>; rel=\"next\", <http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=34>; rel=\"last\""
}
},
"uuid" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99b",
"persistent" : true,
"scenarioName": "scenario-1-search",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-search-2",
"insertionIndex" : 6
}

View File

@@ -0,0 +1,45 @@
{
"id" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99c",
"name" : "search_repositories",
"request" : {
"url" : "/search/repositories?sort=stars&order=desc&q=tetris+language%3Aassembly",
"method" : "GET",
"headers" : {
"Accept" : {
"equalTo" : "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "search_repositories-6.json",
"headers" : {
"Date" : "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-RateLimit-Limit" : "30",
"X-RateLimit-Remaining" : "28",
"X-RateLimit-Reset" : "{{testStartDate offset='2 minutes' format='unix'}}",
"Cache-Control" : "no-cache",
"X-OAuth-Scopes" : "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes" : "",
"X-GitHub-Media-Type" : "unknown, github.v3",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"Vary" : [ "Accept-Encoding, Accept, X-Requested-With", "Accept-Encoding" ],
"X-GitHub-Request-Id" : "D3E6:7BBB:7F46D5:985A70:5ECDA4F2",
"Link" : "<http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=2>; rel=\"next\", <http://localhost:54240/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc&page=34>; rel=\"last\""
}
},
"uuid" : "7b5cb47c-4ce5-4a04-9ef3-caeb2533d99c",
"persistent" : true,
"scenarioName": "scenario-1-search",
"requiredScenarioState": "scenario-1-search-2",
"newScenarioState": "scenario-1-search-3",
"insertionIndex" : 7
}

View File

@@ -0,0 +1,41 @@
{
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 26,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2015-04-20T00:42:30Z",
"type": "Organization",
"total_private_repos": 0,
"owned_private_repos": 0,
"private_gists": 0,
"disk_usage": 147,
"collaborators": 0,
"billing_email": "kk@kohsuke.org",
"default_repository_permission": "none",
"members_can_create_repositories": false,
"two_factor_requirement_enabled": false,
"plan": {
"name": "free",
"space": 976562499,
"private_repos": 0,
"filled_seats": 13,
"seats": 0
}
}

View File

@@ -0,0 +1,55 @@
{
"name": "dummy-team-child",
"id": 3906416,
"node_id": "MDQ6VGVhbTM5MDE2NjQ=",
"slug": "dummy-team-child",
"description": "description",
"privacy": "closed",
"url": "https://api.github.com/organizations/7544739/team/3906416",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/remove-me-team",
"members_url": "https://api.github.com/organizations/7544739/team/3906416/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3906416/repos",
"permission": "admin",
"parent": {
"name": "dummy-team",
"id": 3451996,
"node_id": "MDQ6VGVhbTM0NTE5OTY=",
"slug": "dummy-team",
"description": "Updated by API TestModified",
"privacy": "secret",
"url": "https://api.github.com/organizations/7544739/team/3451996",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/dummy-team",
"members_url": "https://api.github.com/organizations/7544739/team/3451996/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3451996/repos",
"permission": "pull"
},
"created_at": "2020-06-18T08:11:37Z",
"updated_at": "2020-06-18T08:11:37Z",
"members_count": 1,
"repos_count": 0,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 26,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2015-04-20T00:42:30Z",
"type": "Organization"
}
}

View File

@@ -0,0 +1,43 @@
{
"name": "dummy-team",
"id": 3451996,
"node_id": "MDQ6VGVhbTM0NTE5OTY=",
"slug": "dummy-team",
"description": "Updated by API TestModified",
"privacy": "secret",
"url": "https://api.github.com/organizations/7544739/team/3451996",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/dummy-team",
"members_url": "https://api.github.com/organizations/7544739/team/3451996/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3451996/repos",
"permission": "pull",
"created_at": "2019-10-03T21:46:12Z",
"updated_at": "2020-03-17T09:06:57Z",
"members_count": 1,
"repos_count": 1,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 26,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2015-04-20T00:42:30Z",
"type": "Organization"
},
"parent": null
}

View File

@@ -0,0 +1,46 @@
{
"id": "3211dfd0-5228-4cdc-aea3-ab67437f3fd4",
"name": "orgs_hub4j-test-org",
"request": {
"url": "/orgs/hub4j-test-org",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org-2.json",
"headers": {
"Date": "Tue, 17 Mar 2020 09:12:21 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4445",
"X-RateLimit-Reset": "1584436621",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"9d4203e09aeffc9b5325c2a5355275b5\"",
"Last-Modified": "Mon, 20 Apr 2015 00:42:30 GMT",
"X-OAuth-Scopes": "admin:org, admin:public_key, admin:repo_hook, notifications, repo, user",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "F8C7:46E1:A3A21:151ED4:5E709474"
}
},
"uuid": "3211dfd0-5228-4cdc-aea3-ab67437f3fd4",
"persistent": true,
"insertionIndex": 2
}

View File

@@ -0,0 +1,47 @@
{
"id": "96dd9244-d773-4393-b9da-27ac4fef9643",
"name": "orgs_hub4j-test-org_teams",
"request": {
"url": "/orgs/hub4j-test-org/teams",
"method": "POST",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
},
"bodyPatterns": [
{
"equalToJson": "{\n \"parent_team_id\": 3451996,\n \"name\": \"dummy-team-child\",\n \"description\": \"description\",\n \"privacy\": \"closed\"\n}",
"ignoreArrayOrder": true,
"ignoreExtraElements": false
}
]
},
"response": {
"status": 201,
"bodyFileName": "orgs_hub4j-test-org_teams-4.json",
"headers": {
"Date": "Thu, 18 Jun 2020 07:59:20 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "201 Created",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4000",
"X-RateLimit-Reset": "1592470759",
"X-OAuth-Scopes": "admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, read:packages, repo, user, workflow, write:discussion, write:packages",
"X-Accepted-OAuth-Scopes": "admin:org, repo",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding, Accept, X-Requested-With",
"X-GitHub-Request-Id": "C8BA:37155:255562A:5EFB1ED7:2CCF111"
}
},
"uuid": "96dd9244-d773-4393-b9da-27ac4fef9643",
"persistent": true,
"insertionIndex": 4
}

View File

@@ -0,0 +1,49 @@
{
"id": "b239719e-8610-449b-91bd-a35f4cc00424",
"name": "orgs_hub4j-test-org_teams_dummy-team",
"request": {
"url": "/orgs/hub4j-test-org/teams/dummy-team",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org_teams_dummy-team-3.json",
"headers": {
"Date": "Tue, 17 Mar 2020 09:12:22 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4444",
"X-RateLimit-Reset": "1584436622",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"a3be19ac98441e2833990a01948c5bbd\"",
"Last-Modified": "Tue, 17 Mar 2020 09:06:57 GMT",
"X-OAuth-Scopes": "admin:org, admin:public_key, admin:repo_hook, notifications, repo, user",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "F8C7:46E1:A3A28:151EF6:5E709475"
}
},
"uuid": "b239719e-8610-449b-91bd-a35f4cc00424",
"persistent": true,
"scenarioName": "scenario-1-orgs-hub4j-test-org-teams-dummy-team",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-orgs-hub4j-test-org-teams-dummy-team-2",
"insertionIndex": 3
}

View File

@@ -0,0 +1,47 @@
{
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": "Hub4j Test Org Description (this could be null or blank too)",
"name": "Hub4j Test Org Name (this could be null or blank too)",
"company": null,
"blog": "https://hub4j.url.io/could/be/null",
"location": "Hub4j Test Org Location (this could be null or blank too)",
"email": "hub4jtestorgemail@could.be.null.com",
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 12,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2020-06-04T05:56:10Z",
"type": "Organization",
"total_private_repos": 0,
"owned_private_repos": 0,
"private_gists": null,
"disk_usage": null,
"collaborators": null,
"billing_email": null,
"default_repository_permission": null,
"members_can_create_repositories": false,
"two_factor_requirement_enabled": null,
"plan": {
"name": "free",
"space": 976562499,
"private_repos": 10000,
"filled_seats": 19,
"seats": 3
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "dummy-team",
"id": 3451996,
"node_id": "MDQ6VGVhbTM0NTE5OTY=",
"slug": "dummy-team",
"description": "Updated by API TestModified",
"privacy": "closed",
"url": "https://api.github.com/organizations/7544739/team/3451996",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/dummy-team",
"members_url": "https://api.github.com/organizations/7544739/team/3451996/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3451996/repos",
"permission": "pull",
"created_at": "2019-10-03T21:46:12Z",
"updated_at": "2020-06-02T19:31:50Z",
"members_count": 2,
"repos_count": 1,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": "Hub4j Test Org Description (this could be null or blank too)",
"name": "Hub4j Test Org Name (this could be null or blank too)",
"company": null,
"blog": "https://hub4j.url.io/could/be/null",
"location": "Hub4j Test Org Location (this could be null or blank too)",
"email": "hub4jtestorgemail@could.be.null.com",
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 12,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2020-06-04T05:56:10Z",
"type": "Organization"
},
"parent": null
}

View File

@@ -0,0 +1,28 @@
[
{
"name": "child-team-for-dummy",
"id": 3903497,
"node_id": "MDQ6VGVhbTM5MDM0OTc=",
"slug": "child-team-for-dummy",
"description": "to test the fetching of child teams",
"privacy": "closed",
"url": "https://api.github.com/organizations/7544739/team/3903497",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/child-team-for-dummy",
"members_url": "https://api.github.com/organizations/7544739/team/3903497/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3903497/repos",
"permission": "pull",
"parent": {
"name": "dummy-team",
"id": 3451996,
"node_id": "MDQ6VGVhbTM0NTE5OTY=",
"slug": "dummy-team",
"description": "Updated by API TestModified",
"privacy": "closed",
"url": "https://api.github.com/organizations/7544739/team/3451996",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/dummy-team",
"members_url": "https://api.github.com/organizations/7544739/team/3451996/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3451996/repos",
"permission": "pull"
}
}
]

View File

@@ -0,0 +1,46 @@
{
"login": "alexanderkjall",
"id": 647710,
"node_id": "MDQ6VXNlcjY0NzcxMA==",
"avatar_url": "https://avatars2.githubusercontent.com/u/647710?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/alexanderkjall",
"html_url": "https://github.com/alexanderkjall",
"followers_url": "https://api.github.com/users/alexanderkjall/followers",
"following_url": "https://api.github.com/users/alexanderkjall/following{/other_user}",
"gists_url": "https://api.github.com/users/alexanderkjall/gists{/gist_id}",
"starred_url": "https://api.github.com/users/alexanderkjall/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/alexanderkjall/subscriptions",
"organizations_url": "https://api.github.com/users/alexanderkjall/orgs",
"repos_url": "https://api.github.com/users/alexanderkjall/repos",
"events_url": "https://api.github.com/users/alexanderkjall/events{/privacy}",
"received_events_url": "https://api.github.com/users/alexanderkjall/received_events",
"type": "User",
"site_admin": false,
"name": "Alexander Kjäll",
"company": "DI",
"blog": "",
"location": "Oslo",
"email": "alexander.kjall@gmail.com",
"hireable": null,
"bio": null,
"twitter_username": null,
"public_repos": 56,
"public_gists": 6,
"followers": 10,
"following": 7,
"created_at": "2011-03-02T20:31:15Z",
"updated_at": "2020-06-19T06:10:45Z",
"private_gists": 1,
"total_private_repos": 1,
"owned_private_repos": 1,
"disk_usage": 46941,
"collaborators": 0,
"two_factor_authentication": true,
"plan": {
"name": "free",
"space": 976562499,
"collaborators": 0,
"private_repos": 10000
}
}

View File

@@ -0,0 +1,47 @@
{
"id": "4d3be7ef-fe05-4739-9138-d2050a55a479",
"name": "orgs_hub4j-test-org",
"request": {
"url": "/orgs/hub4j-test-org",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org-2.json",
"headers": {
"Date": "Fri, 19 Jun 2020 06:25:15 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4983",
"X-RateLimit-Reset": "1592551194",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With",
"Accept-Encoding"
],
"ETag": "W/\"1cf57cb32512ca7f96e2d9133ecbb8e4\"",
"Last-Modified": "Thu, 04 Jun 2020 05:56:10 GMT",
"X-OAuth-Scopes": "read:discussion, read:enterprise, read:gpg_key, read:org, read:packages, read:public_key, read:repo_hook, read:user",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "3A75:36154:6E3961D:844DC9F:5EEC5A4A"
}
},
"uuid": "4d3be7ef-fe05-4739-9138-d2050a55a479",
"persistent": true,
"insertionIndex": 2
}

View File

@@ -0,0 +1,47 @@
{
"id": "7f56b474-35c0-4a6b-82d6-2ebbd5a88725",
"name": "orgs_hub4j-test-org_teams_dummy-team",
"request": {
"url": "/orgs/hub4j-test-org/teams/dummy-team",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org_teams_dummy-team-3.json",
"headers": {
"Date": "Fri, 19 Jun 2020 06:25:15 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4982",
"X-RateLimit-Reset": "1592551193",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With",
"Accept-Encoding"
],
"ETag": "W/\"df4f58bfeba1d4c5945b3dcd4e9579f9\"",
"Last-Modified": "Tue, 02 Jun 2020 19:31:50 GMT",
"X-OAuth-Scopes": "read:discussion, read:enterprise, read:gpg_key, read:org, read:packages, read:public_key, read:repo_hook, read:user",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "3A75:36154:6E3968D:844DE58:5EEC5A4B"
}
},
"uuid": "7f56b474-35c0-4a6b-82d6-2ebbd5a88725",
"persistent": true,
"insertionIndex": 3
}

View File

@@ -0,0 +1,49 @@
{
"id": "c29c8c17-2529-4266-933e-2fba6569b235",
"name": "teams_3451996_teams",
"request": {
"url": "/teams/3451996/teams",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "teams_3451996_teams-4.json",
"headers": {
"Date": "Fri, 19 Jun 2020 06:25:16 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4981",
"X-RateLimit-Reset": "1592551194",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With",
"Accept-Encoding"
],
"ETag": "W/\"0c83b6a8518d93d48b0d72413e32e65b\"",
"X-OAuth-Scopes": "read:discussion, read:enterprise, read:gpg_key, read:org, read:packages, read:public_key, read:repo_hook, read:user",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Deprecation": "Sat, 01 Feb 2020 00:00:00 GMT",
"Sunset": "Mon, 01 Feb 2021 00:00:00 GMT",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "3A75:36154:6E396FD:844DEDD:5EEC5A4B",
"Link": "<https://developer.github.com/changes/2020-01-21-moving-the-team-api-endpoints/>; rel=\"deprecation\"; type=\"text/html\", <https://api.github.com/organizations/7544739/team/3451996/teams>; rel=\"alternate\""
}
},
"uuid": "c29c8c17-2529-4266-933e-2fba6569b235",
"persistent": true,
"insertionIndex": 4
}

View File

@@ -0,0 +1,47 @@
{
"id": "f995e14b-7512-443c-8eca-d623b67e63f9",
"name": "user",
"request": {
"url": "/user",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "user-1.json",
"headers": {
"Date": "Fri, 19 Jun 2020 06:25:14 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4985",
"X-RateLimit-Reset": "1592551193",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With",
"Accept-Encoding"
],
"ETag": "W/\"89ff4a8b6ccfac0c1ba328a1c063ea1f\"",
"Last-Modified": "Fri, 19 Jun 2020 06:10:45 GMT",
"X-OAuth-Scopes": "read:discussion, read:enterprise, read:gpg_key, read:org, read:packages, read:public_key, read:repo_hook, read:user",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "3A75:36154:6E394AB:844DC3B:5EEC5A4A"
}
},
"uuid": "f995e14b-7512-443c-8eca-d623b67e63f9",
"persistent": true,
"insertionIndex": 1
}

View File

@@ -0,0 +1,47 @@
{
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": "Hub4j Test Org Description (this could be null or blank too)",
"name": "Hub4j Test Org Name (this could be null or blank too)",
"company": null,
"blog": "https://hub4j.url.io/could/be/null",
"location": "Hub4j Test Org Location (this could be null or blank too)",
"email": "hub4jtestorgemail@could.be.null.com",
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 12,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2020-06-04T05:56:10Z",
"type": "Organization",
"total_private_repos": 0,
"owned_private_repos": 0,
"private_gists": 0,
"disk_usage": 148,
"collaborators": 0,
"billing_email": "kk@kohsuke.org",
"default_repository_permission": "none",
"members_can_create_repositories": false,
"two_factor_requirement_enabled": false,
"plan": {
"name": "free",
"space": 976562499,
"private_repos": 10000,
"filled_seats": 18,
"seats": 3
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "simple-team",
"id": 3947450,
"node_id": "MDQ6VGVhbTM5NDc0NTA=",
"slug": "simple-team",
"description": "A simple team with no children",
"privacy": "closed",
"url": "https://api.github.com/organizations/7544739/team/3947450",
"html_url": "https://github.com/orgs/hub4j-test-org/teams/simple-team",
"members_url": "https://api.github.com/organizations/7544739/team/3947450/members{/member}",
"repositories_url": "https://api.github.com/organizations/7544739/team/3947450/repos",
"permission": "pull",
"created_at": "2020-07-15T20:36:47Z",
"updated_at": "2020-07-15T20:36:47Z",
"members_count": 1,
"repos_count": 0,
"organization": {
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"description": "Hub4j Test Org Description (this could be null or blank too)",
"name": "Hub4j Test Org Name (this could be null or blank too)",
"company": null,
"blog": "https://hub4j.url.io/could/be/null",
"location": "Hub4j Test Org Location (this could be null or blank too)",
"email": "hub4jtestorgemail@could.be.null.com",
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 12,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2020-06-04T05:56:10Z",
"type": "Organization"
},
"parent": null
}

View File

@@ -0,0 +1,46 @@
{
"login": "bitwiseman",
"id": 1958953,
"node_id": "MDQ6VXNlcjE5NTg5NTM=",
"avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/bitwiseman",
"html_url": "https://github.com/bitwiseman",
"followers_url": "https://api.github.com/users/bitwiseman/followers",
"following_url": "https://api.github.com/users/bitwiseman/following{/other_user}",
"gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}",
"starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions",
"organizations_url": "https://api.github.com/users/bitwiseman/orgs",
"repos_url": "https://api.github.com/users/bitwiseman/repos",
"events_url": "https://api.github.com/users/bitwiseman/events{/privacy}",
"received_events_url": "https://api.github.com/users/bitwiseman/received_events",
"type": "User",
"site_admin": false,
"name": "Liam Newman",
"company": "Cloudbees, Inc.",
"blog": "",
"location": "Seattle, WA, USA",
"email": "bitwiseman@gmail.com",
"hireable": null,
"bio": null,
"twitter_username": "bitwiseman",
"public_repos": 196,
"public_gists": 7,
"followers": 165,
"following": 9,
"created_at": "2012-07-11T20:38:33Z",
"updated_at": "2020-07-14T20:54:47Z",
"private_gists": 19,
"total_private_repos": 14,
"owned_private_repos": 0,
"disk_usage": 33700,
"collaborators": 0,
"two_factor_authentication": true,
"plan": {
"name": "free",
"space": 976562499,
"collaborators": 0,
"private_repos": 10000
}
}

View File

@@ -0,0 +1,46 @@
{
"id": "db9b96a5-fe2c-487d-8fbe-777c821a637a",
"name": "orgs_hub4j-test-org",
"request": {
"url": "/orgs/hub4j-test-org",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org-2.json",
"headers": {
"Server": "GitHub.com",
"Date": "Wed, 15 Jul 2020 20:38:33 GMT",
"Content-Type": "application/json; charset=utf-8",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4988",
"X-RateLimit-Reset": "1594848477",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"6bd323dd4ab2a01dae2464621246c3cd\"",
"Last-Modified": "Thu, 04 Jun 2020 05:56:10 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "CB6B:7F44:5E37B7:DCCBC7:5F0F6948"
}
},
"uuid": "db9b96a5-fe2c-487d-8fbe-777c821a637a",
"persistent": true,
"insertionIndex": 2
}

View File

@@ -0,0 +1,46 @@
{
"id": "015dd9a8-6fb4-4ab0-9053-15905e3a80be",
"name": "orgs_hub4j-test-org_teams_simple-team",
"request": {
"url": "/orgs/hub4j-test-org/teams/simple-team",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "orgs_hub4j-test-org_teams_simple-team-3.json",
"headers": {
"Server": "GitHub.com",
"Date": "Wed, 15 Jul 2020 20:38:33 GMT",
"Content-Type": "application/json; charset=utf-8",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4987",
"X-RateLimit-Reset": "1594848477",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"a5eb4fddb71a5d9f0af1fdcf97a20361\"",
"Last-Modified": "Wed, 15 Jul 2020 20:36:47 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "CB6B:7F44:5E37BE:DCCC34:5F0F6949"
}
},
"uuid": "015dd9a8-6fb4-4ab0-9053-15905e3a80be",
"persistent": true,
"insertionIndex": 3
}

View File

@@ -0,0 +1,48 @@
{
"id": "7a45d54e-c5da-4fd9-bb6a-121d5bc2381c",
"name": "teams_3947450_teams",
"request": {
"url": "/teams/3947450/teams",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"body": "[]",
"headers": {
"Server": "GitHub.com",
"Date": "Wed, 15 Jul 2020 20:38:33 GMT",
"Content-Type": "application/json; charset=utf-8",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4986",
"X-RateLimit-Reset": "1594848477",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "\"c2ec59aeeea67fff8edf681155a22565\"",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
"X-GitHub-Media-Type": "unknown, github.v3",
"Deprecation": "Sat, 01 Feb 2020 00:00:00 GMT",
"Sunset": "Mon, 01 Feb 2021 00:00:00 GMT",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "CB6B:7F44:5E37C8:DCCC46:5F0F6949",
"Link": "<https://developer.github.com/changes/2020-01-21-moving-the-team-api-endpoints/>; rel=\"deprecation\"; type=\"text/html\", <https://api.github.com/organizations/7544739/team/3947450/teams>; rel=\"alternate\""
}
},
"uuid": "7a45d54e-c5da-4fd9-bb6a-121d5bc2381c",
"persistent": true,
"insertionIndex": 4
}

View File

@@ -0,0 +1,46 @@
{
"id": "d3cc8cd3-77b2-49be-bfed-02cf7a5cce56",
"name": "user",
"request": {
"url": "/user",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "user-1.json",
"headers": {
"Server": "GitHub.com",
"Date": "Wed, 15 Jul 2020 20:38:32 GMT",
"Content-Type": "application/json; charset=utf-8",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4990",
"X-RateLimit-Reset": "1594848477",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"a4dab08bcf40f1302ada7a08825695c8\"",
"Last-Modified": "Tue, 14 Jul 2020 20:54:47 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "CB6B:7F44:5E378A:DCCBB5:5F0F6948"
}
},
"uuid": "d3cc8cd3-77b2-49be-bfed-02cf7a5cce56",
"persistent": true,
"insertionIndex": 1
}

View File

@@ -0,0 +1,6 @@
**/extras/**
**/AbuseLimitHandlerTest
**/GHRateLimitTest
**/RequesterRetryTest
**/RateLimitCheckerTest
**/RateLimitHandlerTest