From c333903b4a4a860bfebdd8bfa43d0245aaa4fc5a Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Mon, 30 Mar 2020 17:22:44 -0700 Subject: [PATCH 1/3] Http header field names must be case-insensitive GitHub has started changing their headers from capitalized words to all lowercase. A recent change made the header fields querying case-senstive which broke gzip content detection. This was not caught by tests because recorded files remain unchanged. It is also possible that WireMock is auto-capitalizing. This fixes the case-sensitivity issue and also extends that funcionality to anyone consuming the headers generated by ResponseInfo. Fixes #751 --- .../org/kohsuke/github/GitHubResponse.java | 13 +++++++++-- .../java/org/kohsuke/github/GHObjectTest.java | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHubResponse.java b/src/main/java/org/kohsuke/github/GitHubResponse.java index 50bb35d4ff..a73f9d8016 100644 --- a/src/main/java/org/kohsuke/github/GitHubResponse.java +++ b/src/main/java/org/kohsuke/github/GitHubResponse.java @@ -12,9 +12,10 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; -import java.util.HashMap; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.TreeMap; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -206,6 +207,9 @@ interface BodyHandler { */ static abstract class ResponseInfo { + private static final Comparator nullableCaseInsensitiveComparator = Comparator + .nullsFirst(String.CASE_INSENSITIVE_ORDER); + private final int statusCode; @Nonnull private final GitHubRequest request; @@ -217,7 +221,12 @@ protected ResponseInfo(@Nonnull GitHubRequest request, @Nonnull Map> headers) { this.request = request; this.statusCode = statusCode; - this.headers = Collections.unmodifiableMap(new HashMap<>(headers)); + + // Response header field names must be case-insensitive. + TreeMap> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator); + caseInsensitiveMap.putAll(headers); + + this.headers = Collections.unmodifiableMap(caseInsensitiveMap); } /** diff --git a/src/test/java/org/kohsuke/github/GHObjectTest.java b/src/test/java/org/kohsuke/github/GHObjectTest.java index 370f13950b..8e5a36e12f 100644 --- a/src/test/java/org/kohsuke/github/GHObjectTest.java +++ b/src/test/java/org/kohsuke/github/GHObjectTest.java @@ -2,6 +2,8 @@ import org.junit.Test; +import java.util.Objects; + import static org.hamcrest.Matchers.*; public class GHObjectTest extends org.kohsuke.github.AbstractGitHubWireMockTest { @@ -16,5 +18,26 @@ public void test_toString() throws Exception { // getResponseHeaderFields is deprecated but we should not break it. assertThat(org.getResponseHeaderFields(), notNullValue()); assertThat(org.getResponseHeaderFields().get("Cache-Control").get(0), is("private, max-age=60, s-maxage=60")); + + // Header field names must be case-insensitive + assertThat(org.getResponseHeaderFields().containsKey("CacHe-ContrOl"), is(true)); + + // The KeySet from header fields should also be case-insensitive + assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); + assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); + + assertThat(org.getResponseHeaderFields().get("cachE-cOntrol").get(0), is("private, max-age=60, s-maxage=60")); + + // GitHub has started changing their headers to all lowercase. + // For this test we want the field names to be with mixed-case (harder to do comparison). + // Ensure that it remains that way, if test resources are ever refreshed. + boolean found = false; + for (String key : org.getResponseHeaderFields().keySet()) { + if (Objects.equals("Cache-Control", key)) { + found = true; + break; + } + } + assertThat("Must have the literal expected string 'Cache-Control' for header field name", found); } } From f60bb41ad9cb89f6b938597f075ec448e6647309 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Mon, 30 Mar 2020 19:11:36 -0700 Subject: [PATCH 2/3] Move tests around for clarity --- .../java/org/kohsuke/github/GHObjectTest.java | 23 ---------- .../java/org/kohsuke/github/GitHubTest.java | 45 ++++++++++++++++++ .../__files/orgs_github-api-test-org-2.json | 41 +++++++++++++++++ .../wiremock/gzip/__files/user-1.json | 45 ++++++++++++++++++ .../mappings/orgs_github-api-test-org-2.json | 46 +++++++++++++++++++ .../wiremock/gzip}/mappings/user-1.json | 20 ++++---- .../__files/orgs_github-api-test-org-2.json | 41 +++++++++++++++++ .../testHeaderFieldName/__files/user-1.json | 45 ++++++++++++++++++ .../mappings/orgs_github-api-test-org-2.json | 46 +++++++++++++++++++ .../testHeaderFieldName/mappings/user-1.json | 46 +++++++++++++++++++ 10 files changed, 364 insertions(+), 34 deletions(-) create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/orgs_github-api-test-org-2.json create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/user-1.json create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/orgs_github-api-test-org-2.json rename src/test/resources/org/kohsuke/github/{AppTest/wiremock/directoryListing => GitHubTest/wiremock/gzip}/mappings/user-1.json (63%) create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/orgs_github-api-test-org-2.json create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/user-1.json create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/orgs_github-api-test-org-2.json create mode 100644 src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/user-1.json diff --git a/src/test/java/org/kohsuke/github/GHObjectTest.java b/src/test/java/org/kohsuke/github/GHObjectTest.java index 8e5a36e12f..370f13950b 100644 --- a/src/test/java/org/kohsuke/github/GHObjectTest.java +++ b/src/test/java/org/kohsuke/github/GHObjectTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import java.util.Objects; - import static org.hamcrest.Matchers.*; public class GHObjectTest extends org.kohsuke.github.AbstractGitHubWireMockTest { @@ -18,26 +16,5 @@ public void test_toString() throws Exception { // getResponseHeaderFields is deprecated but we should not break it. assertThat(org.getResponseHeaderFields(), notNullValue()); assertThat(org.getResponseHeaderFields().get("Cache-Control").get(0), is("private, max-age=60, s-maxage=60")); - - // Header field names must be case-insensitive - assertThat(org.getResponseHeaderFields().containsKey("CacHe-ContrOl"), is(true)); - - // The KeySet from header fields should also be case-insensitive - assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); - assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); - - assertThat(org.getResponseHeaderFields().get("cachE-cOntrol").get(0), is("private, max-age=60, s-maxage=60")); - - // GitHub has started changing their headers to all lowercase. - // For this test we want the field names to be with mixed-case (harder to do comparison). - // Ensure that it remains that way, if test resources are ever refreshed. - boolean found = false; - for (String key : org.getResponseHeaderFields().keySet()) { - if (Objects.equals("Cache-Control", key)) { - found = true; - break; - } - } - assertThat("Must have the literal expected string 'Cache-Control' for header field name", found); } } diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index ccdcbe3b2e..565c76044a 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -8,6 +8,8 @@ import java.util.*; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.kohsuke.github.GHMarketplaceAccountType.ORGANIZATION; /** @@ -162,4 +164,47 @@ public void getMyMarketplacePurchases() throws IOException { assertNull(account.getOrganizationBillingEmail()); } } + + @Test + public void gzip() throws Exception { + + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + // getResponseHeaderFields is deprecated but we'll use it for testing. + assertThat(org.getResponseHeaderFields(), notNullValue()); + + // WireMock should automatically gzip all responses + assertThat(org.getResponseHeaderFields().get("Content-Encoding").get(0), is("gzip")); + assertThat(org.getResponseHeaderFields().get("Content-eNcoding").get(0), is("gzip")); + } + + @Test + public void testHeaderFieldName() throws Exception { + + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + + // getResponseHeaderFields is deprecated but we'll use it for testing. + assertThat(org.getResponseHeaderFields(), notNullValue()); + + // Header field names must be case-insensitive + assertThat(org.getResponseHeaderFields().containsKey("CacHe-ContrOl"), is(true)); + + // The KeySet from header fields should also be case-insensitive + assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); + assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true)); + + assertThat(org.getResponseHeaderFields().get("cachE-cOntrol").get(0), is("private, max-age=60, s-maxage=60")); + + // GitHub has started changing their headers to all lowercase. + // For this test we want the field names to be with mixed-case (harder to do comparison). + // Ensure that it remains that way, if test resources are ever refreshed. + boolean found = false; + for (String key : org.getResponseHeaderFields().keySet()) { + if (Objects.equals("Cache-Control", key)) { + found = true; + break; + } + } + assertThat("Must have the literal expected string 'Cache-Control' for header field name", found); + } } diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/orgs_github-api-test-org-2.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/orgs_github-api-test-org-2.json new file mode 100644 index 0000000000..6745adcb15 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/orgs_github-api-test-org-2.json @@ -0,0 +1,41 @@ +{ + "login": "github-api-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/github-api-test-org", + "repos_url": "https://api.github.com/orgs/github-api-test-org/repos", + "events_url": "https://api.github.com/orgs/github-api-test-org/events", + "hooks_url": "https://api.github.com/orgs/github-api-test-org/hooks", + "issues_url": "https://api.github.com/orgs/github-api-test-org/issues", + "members_url": "https://api.github.com/orgs/github-api-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/github-api-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": 25, + "public_gists": 0, + "followers": 0, + "following": 0, + "html_url": "https://github.com/github-api-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": 15, + "seats": 0 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/user-1.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/user-1.json new file mode 100644 index 0000000000..08a55091ae --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/__files/user-1.json @@ -0,0 +1,45 @@ +{ + "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": "https://twitter.com/bitwiseman", + "public_repos": 181, + "public_gists": 7, + "followers": 151, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2020-03-27T19:14:56Z", + "private_gists": 8, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/orgs_github-api-test-org-2.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/orgs_github-api-test-org-2.json new file mode 100644 index 0000000000..8a760db5a2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/orgs_github-api-test-org-2.json @@ -0,0 +1,46 @@ +{ + "id": "6b1390ac-fba9-4e09-9b33-1915d6aad42d", + "name": "orgs_github-api-test-org", + "request": { + "url": "/orgs/github-api-test-org", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "orgs_github-api-test-org-2.json", + "headers": { + "Date": "Tue, 31 Mar 2020 01:58:58 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4947", + "X-RateLimit-Reset": "1585620720", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"19a1dc3fabba80fa04e5602bb1e9457b\"", + "Last-Modified": "Mon, 20 Apr 2015 00:42:30 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": "DBDB:5BD4:C4C3:F7A4:5E82A3E2" + } + }, + "uuid": "6b1390ac-fba9-4e09-9b33-1915d6aad42d", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/directoryListing/mappings/user-1.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/user-1.json similarity index 63% rename from src/test/resources/org/kohsuke/github/AppTest/wiremock/directoryListing/mappings/user-1.json rename to src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/user-1.json index 82257be851..1ca7a65a3b 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/directoryListing/mappings/user-1.json +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/gzip/mappings/user-1.json @@ -1,5 +1,5 @@ { - "id": "0cdbd9dc-1c6f-42f5-a4b5-9d5472b2a56b", + "id": "1f83f6e6-3e0f-4529-a615-9084272bae98", "name": "user", "request": { "url": "/user", @@ -14,35 +14,33 @@ "status": 200, "bodyFileName": "user-1.json", "headers": { - "Date": "Sat, 26 Oct 2019 01:27:31 GMT", + "Date": "Tue, 31 Mar 2020 01:58:58 GMT", "Content-Type": "application/json; charset=utf-8", "Server": "GitHub.com", "Status": "200 OK", "X-RateLimit-Limit": "5000", - "X-RateLimit-Remaining": "4436", - "X-RateLimit-Reset": "1572055286", + "X-RateLimit-Remaining": "4949", + "X-RateLimit-Reset": "1585620721", "Cache-Control": "private, max-age=60, s-maxage=60", "Vary": [ "Accept, Authorization, Cookie, X-GitHub-OTP", - "Accept-Encoding" + "Accept-Encoding, Accept, X-Requested-With" ], - "ETag": "W/\"8c3d3dcf6fc5f9edaf26c902295396e5\"", - "Last-Modified": "Tue, 24 Sep 2019 19:32:29 GMT", + "ETag": "W/\"740bb7db37d5437d08ea1aa5e652cf37\"", + "Last-Modified": "Fri, 27 Mar 2020 19:14:56 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", - "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", - "Access-Control-Allow-Origin": "*", "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": "CA8F:3649:E32786:1091CE0:5DB3A103" + "X-GitHub-Request-Id": "DBDB:5BD4:C4C2:F7A3:5E82A3E1" } }, - "uuid": "0cdbd9dc-1c6f-42f5-a4b5-9d5472b2a56b", + "uuid": "1f83f6e6-3e0f-4529-a615-9084272bae98", "persistent": true, "insertionIndex": 1 } \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/orgs_github-api-test-org-2.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/orgs_github-api-test-org-2.json new file mode 100644 index 0000000000..6745adcb15 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/orgs_github-api-test-org-2.json @@ -0,0 +1,41 @@ +{ + "login": "github-api-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "url": "https://api.github.com/orgs/github-api-test-org", + "repos_url": "https://api.github.com/orgs/github-api-test-org/repos", + "events_url": "https://api.github.com/orgs/github-api-test-org/events", + "hooks_url": "https://api.github.com/orgs/github-api-test-org/hooks", + "issues_url": "https://api.github.com/orgs/github-api-test-org/issues", + "members_url": "https://api.github.com/orgs/github-api-test-org/members{/member}", + "public_members_url": "https://api.github.com/orgs/github-api-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": 25, + "public_gists": 0, + "followers": 0, + "following": 0, + "html_url": "https://github.com/github-api-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": 15, + "seats": 0 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/user-1.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/user-1.json new file mode 100644 index 0000000000..08a55091ae --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/__files/user-1.json @@ -0,0 +1,45 @@ +{ + "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": "https://twitter.com/bitwiseman", + "public_repos": 181, + "public_gists": 7, + "followers": 151, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2020-03-27T19:14:56Z", + "private_gists": 8, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/orgs_github-api-test-org-2.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/orgs_github-api-test-org-2.json new file mode 100644 index 0000000000..095fb5fcd8 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/orgs_github-api-test-org-2.json @@ -0,0 +1,46 @@ +{ + "id": "a0c589c7-f143-4e8d-8604-39b8d5270128", + "name": "orgs_github-api-test-org", + "request": { + "url": "/orgs/github-api-test-org", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "orgs_github-api-test-org-2.json", + "headers": { + "Date": "Tue, 31 Mar 2020 01:59:34 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4941", + "X-RateLimit-Reset": "1585620720", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"19a1dc3fabba80fa04e5602bb1e9457b\"", + "Last-Modified": "Mon, 20 Apr 2015 00:42:30 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": "DBF1:23DB:612D9:77DEB:5E82A406" + } + }, + "uuid": "a0c589c7-f143-4e8d-8604-39b8d5270128", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/user-1.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/user-1.json new file mode 100644 index 0000000000..941c914605 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/testHeaderFieldName/mappings/user-1.json @@ -0,0 +1,46 @@ +{ + "id": "043c1f83-d0e2-4217-867b-aaa384fcdf4d", + "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": "Tue, 31 Mar 2020 01:59:34 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4943", + "X-RateLimit-Reset": "1585620721", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"740bb7db37d5437d08ea1aa5e652cf37\"", + "Last-Modified": "Fri, 27 Mar 2020 19:14:56 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": "DBF1:23DB:612CC:77DE5:5E82A406" + } + }, + "uuid": "043c1f83-d0e2-4217-867b-aaa384fcdf4d", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file From c9cdf5d03ea50f564166d42343d83b4a42cf0021 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Mon, 30 Mar 2020 19:55:19 -0700 Subject: [PATCH 3/3] Make rate limit tests tolerant of one second timing differences Fixes #760 --- .../java/org/kohsuke/github/GHRateLimitTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/kohsuke/github/GHRateLimitTest.java b/src/test/java/org/kohsuke/github/GHRateLimitTest.java index 9a6d657582..fccca58950 100644 --- a/src/test/java/org/kohsuke/github/GHRateLimitTest.java +++ b/src/test/java/org/kohsuke/github/GHRateLimitTest.java @@ -10,7 +10,7 @@ import java.time.Duration; import java.util.Date; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.hamcrest.core.IsInstanceOf.instanceOf; /** @@ -163,16 +163,20 @@ private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) { } private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining, boolean changedResetDate) { - // newer or unchange - int resetComparisionValue = changedResetDate ? 1 : 0; - // Basic checks of values assertThat(rateLimit, notNullValue()); assertThat(rateLimit.getLimit(), equalTo(previousLimit.getLimit())); assertThat(rateLimit.getRemaining(), equalTo(remaining)); // Check that the reset date of the current limit is not older than the previous one - assertThat(rateLimit.getResetDate().compareTo(previousLimit.getResetDate()), equalTo(resetComparisionValue)); + long diffMillis = rateLimit.getResetDate().getTime() - previousLimit.getResetDate().getTime(); + + assertThat(diffMillis, greaterThanOrEqualTo(0L)); + if (changedResetDate) { + assertThat(diffMillis, greaterThan(1000L)); + } else { + assertThat(diffMillis, lessThanOrEqualTo(1000L)); + } // Additional checks for record values assertThat(rateLimit.getCore().getLimit(), equalTo(rateLimit.getLimit()));