From e8d69e7d0c6b617bd0715c75b0004094ac4fec5c Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Mon, 27 Jan 2025 14:31:24 +0100 Subject: [PATCH] Extend `Rate` and Rate Limiting with `X-Ratelimit-Used` and `X-Ratelimit-Resource` headers (#3453) --- github/github-stringify_test.go | 4 +- github/github.go | 8 +++ github/github_test.go | 48 ++++++++++++++++ github/rate_limit.go | 14 ++++- github/rate_limit_test.go | 97 +++++++++++++++++++++++++-------- 5 files changed, 143 insertions(+), 28 deletions(-) diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index 706e45324e0..ccf4de8dfd5 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1500,9 +1500,11 @@ func TestRate_String(t *testing.T) { v := Rate{ Limit: 0, Remaining: 0, + Used: 0, Reset: Timestamp{}, + Resource: "", } - want := `github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}` + want := `github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}` if got := v.String(); got != want { t.Errorf("Rate.String = %v, want %v", got, want) } diff --git a/github/github.go b/github/github.go index ab74c5b72da..b25e214a47b 100644 --- a/github/github.go +++ b/github/github.go @@ -38,7 +38,9 @@ const ( headerAPIVersion = "X-Github-Api-Version" headerRateLimit = "X-Ratelimit-Limit" headerRateRemaining = "X-Ratelimit-Remaining" + headerRateUsed = "X-Ratelimit-Used" headerRateReset = "X-Ratelimit-Reset" + headerRateResource = "X-Ratelimit-Resource" headerOTP = "X-Github-Otp" headerRetryAfter = "Retry-After" @@ -763,11 +765,17 @@ func parseRate(r *http.Response) Rate { if remaining := r.Header.Get(headerRateRemaining); remaining != "" { rate.Remaining, _ = strconv.Atoi(remaining) } + if used := r.Header.Get(headerRateUsed); used != "" { + rate.Used, _ = strconv.Atoi(used) + } if reset := r.Header.Get(headerRateReset); reset != "" { if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { rate.Reset = Timestamp{time.Unix(v, 0)} } } + if resource := r.Header.Get(headerRateResource); resource != "" { + rate.Resource = resource + } return rate } diff --git a/github/github_test.go b/github/github_test.go index 7116636b691..77dffe57ab7 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -1182,7 +1182,9 @@ func TestDo_rateLimit(t *testing.T) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "59") + w.Header().Set(headerRateUsed, "1") w.Header().Set(headerRateReset, "1372700873") + w.Header().Set(headerRateResource, "core") }) req, _ := client.NewRequest("GET", ".", nil) @@ -1197,10 +1199,16 @@ func TestDo_rateLimit(t *testing.T) { if got, want := resp.Rate.Remaining, 59; got != want { t.Errorf("Client rate remaining = %v, want %v", got, want) } + if got, want := resp.Rate.Used, 1; got != want { + t.Errorf("Client rate used = %v, want %v", got, want) + } reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) if !resp.Rate.Reset.UTC().Equal(reset) { t.Errorf("Client rate reset = %v, want %v", resp.Rate.Reset.UTC(), reset) } + if got, want := resp.Rate.Resource, "core"; got != want { + t.Errorf("Client rate resource = %v, want %v", got, want) + } } func TestDo_rateLimitCategory(t *testing.T) { @@ -1288,7 +1296,9 @@ func TestDo_rateLimit_errorResponse(t *testing.T) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "59") + w.Header().Set(headerRateUsed, "1") w.Header().Set(headerRateReset, "1372700873") + w.Header().Set(headerRateResource, "core") http.Error(w, "Bad Request", 400) }) @@ -1307,10 +1317,16 @@ func TestDo_rateLimit_errorResponse(t *testing.T) { if got, want := resp.Rate.Remaining, 59; got != want { t.Errorf("Client rate remaining = %v, want %v", got, want) } + if got, want := resp.Rate.Used, 1; got != want { + t.Errorf("Client rate used = %v, want %v", got, want) + } reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) if !resp.Rate.Reset.UTC().Equal(reset) { t.Errorf("Client rate reset = %v, want %v", resp.Rate.Reset, reset) } + if got, want := resp.Rate.Resource, "core"; got != want { + t.Errorf("Client rate resource = %v, want %v", got, want) + } } // Ensure *RateLimitError is returned when API rate limit is exceeded. @@ -1321,7 +1337,9 @@ func TestDo_rateLimit_rateLimitError(t *testing.T) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, "1372700873") + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1347,10 +1365,16 @@ func TestDo_rateLimit_rateLimitError(t *testing.T) { if got, want := rateLimitErr.Rate.Remaining, 0; got != want { t.Errorf("rateLimitErr rate remaining = %v, want %v", got, want) } + if got, want := rateLimitErr.Rate.Used, 60; got != want { + t.Errorf("rateLimitErr rate used = %v, want %v", got, want) + } reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) if !rateLimitErr.Rate.Reset.UTC().Equal(reset) { t.Errorf("rateLimitErr rate reset = %v, want %v", rateLimitErr.Rate.Reset.UTC(), reset) } + if got, want := rateLimitErr.Rate.Resource, "core"; got != want { + t.Errorf("rateLimitErr rate resource = %v, want %v", got, want) + } } // Ensure a network call is not made when it's known that API rate limit is still exceeded. @@ -1363,7 +1387,9 @@ func TestDo_rateLimit_noNetworkCall(t *testing.T) { mux.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) { w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1406,9 +1432,15 @@ func TestDo_rateLimit_noNetworkCall(t *testing.T) { if got, want := rateLimitErr.Rate.Remaining, 0; got != want { t.Errorf("rateLimitErr rate remaining = %v, want %v", got, want) } + if got, want := rateLimitErr.Rate.Used, 60; got != want { + t.Errorf("rateLimitErr rate used = %v, want %v", got, want) + } if !rateLimitErr.Rate.Reset.UTC().Equal(reset) { t.Errorf("rateLimitErr rate reset = %v, want %v", rateLimitErr.Rate.Reset.UTC(), reset) } + if got, want := rateLimitErr.Rate.Resource, "core"; got != want { + t.Errorf("rateLimitErr rate resource = %v, want %v", got, want) + } } // Ignore rate limit headers if the response was served from cache. @@ -1423,7 +1455,9 @@ func TestDo_rateLimit_ignoredFromCache(t *testing.T) { w.Header().Set("X-From-Cache", "1") w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1470,7 +1504,9 @@ func TestDo_rateLimit_sleepUntilResponseResetLimit(t *testing.T) { firstRequest = false w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1481,7 +1517,9 @@ func TestDo_rateLimit_sleepUntilResponseResetLimit(t *testing.T) { } w.Header().Set(headerRateLimit, "5000") w.Header().Set(headerRateRemaining, "5000") + w.Header().Set(headerRateUsed, "0") w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{}`) @@ -1510,7 +1548,9 @@ func TestDo_rateLimit_sleepUntilResponseResetLimitRetryOnce(t *testing.T) { requestCount++ w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1542,7 +1582,9 @@ func TestDo_rateLimit_sleepUntilClientResetLimit(t *testing.T) { requestCount++ w.Header().Set(headerRateLimit, "5000") w.Header().Set(headerRateRemaining, "5000") + w.Header().Set(headerRateUsed, "0") w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{}`) @@ -1573,7 +1615,9 @@ func TestDo_rateLimit_abortSleepContextCancelled(t *testing.T) { requestCount++ w.Header().Set(headerRateLimit, "60") w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateUsed, "60") w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, `{ @@ -1606,7 +1650,9 @@ func TestDo_rateLimit_abortSleepContextCancelledClientLimit(t *testing.T) { requestCount++ w.Header().Set(headerRateLimit, "5000") w.Header().Set(headerRateRemaining, "5000") + w.Header().Set(headerRateUsed, "0") w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix())) + w.Header().Set(headerRateResource, "core") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{}`) @@ -1926,7 +1972,9 @@ func TestCheckResponse_RateLimit(t *testing.T) { } res.Header.Set(headerRateLimit, "60") res.Header.Set(headerRateRemaining, "0") + res.Header.Set(headerRateUsed, "1") res.Header.Set(headerRateReset, "243424") + res.Header.Set(headerRateResource, "core") err := CheckResponse(res).(*RateLimitError) diff --git a/github/rate_limit.go b/github/rate_limit.go index d55c545e7a5..6236eba8fbb 100644 --- a/github/rate_limit.go +++ b/github/rate_limit.go @@ -12,14 +12,22 @@ type RateLimitService service // Rate represents the rate limit for the current client. type Rate struct { - // The number of requests per hour the client is currently limited to. + // The maximum number of requests that you can make per hour. Limit int `json:"limit"` - // The number of remaining requests the client can make this hour. + // The number of requests remaining in the current rate limit window. Remaining int `json:"remaining"` - // The time at which the current rate limit will reset. + // The number of requests you have made in the current rate limit window. + Used int `json:"used"` + + // The time at which the current rate limit window resets, in UTC epoch seconds. Reset Timestamp `json:"reset"` + + // The rate limit resource that the request counted against. + // For more information about the different resources, see REST API endpoints for rate limits. + // GitHub API docs: https://docs.github.com/en/rest/rate-limit/rate-limit#get-rate-limit-status-for-the-authenticated-user + Resource string `json:"resource,omitempty"` } func (r Rate) String() string { diff --git a/github/rate_limit_test.go b/github/rate_limit_test.go index 241b8ce0982..8e7f2c83563 100644 --- a/github/rate_limit_test.go +++ b/github/rate_limit_test.go @@ -30,7 +30,7 @@ func TestRateLimits_String(t *testing.T) { CodeSearch: &Rate{}, AuditLog: &Rate{}, } - want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, Search:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, GraphQL:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SourceImport:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SCIM:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeSearch:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, AuditLog:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}` + want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, Search:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, GraphQL:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, SourceImport:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, SCIM:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, CodeSearch:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}, AuditLog:github.Rate{Limit:0, Remaining:0, Used:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Resource:""}}` if got := v.String(); got != want { t.Errorf("RateLimits.String = %v, want %v", got, want) } @@ -43,17 +43,17 @@ func TestRateLimits(t *testing.T) { mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") fmt.Fprint(w, `{"resources":{ - "core": {"limit":2,"remaining":1,"reset":1372700873}, - "search": {"limit":3,"remaining":2,"reset":1372700874}, - "graphql": {"limit":4,"remaining":3,"reset":1372700875}, - "integration_manifest": {"limit":5,"remaining":4,"reset":1372700876}, - "source_import": {"limit":6,"remaining":5,"reset":1372700877}, - "code_scanning_upload": {"limit":7,"remaining":6,"reset":1372700878}, - "actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879}, - "scim": {"limit":9,"remaining":8,"reset":1372700880}, - "dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881}, - "code_search": {"limit":11,"remaining":10,"reset":1372700882}, - "audit_log": {"limit": 12,"remaining":11,"reset":1372700883} + "core": {"limit":2,"remaining":1,"used":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"used":1,"reset":1372700874}, + "graphql": {"limit":4,"remaining":3,"used":1,"reset":1372700875}, + "integration_manifest": {"limit":5,"remaining":4,"used":1,"reset":1372700876}, + "source_import": {"limit":6,"remaining":5,"used":1,"reset":1372700877}, + "code_scanning_upload": {"limit":7,"remaining":6,"used":1,"reset":1372700878}, + "actions_runner_registration": {"limit":8,"remaining":7,"used":1,"reset":1372700879}, + "scim": {"limit":9,"remaining":8,"used":1,"reset":1372700880}, + "dependency_snapshots": {"limit":10,"remaining":9,"used":1,"reset":1372700881}, + "code_search": {"limit":11,"remaining":10,"used":1,"reset":1372700882}, + "audit_log": {"limit": 12,"remaining":11,"used":1,"reset":1372700883} }}`) }) @@ -67,56 +67,67 @@ func TestRateLimits(t *testing.T) { Core: &Rate{ Limit: 2, Remaining: 1, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, }, Search: &Rate{ Limit: 3, Remaining: 2, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, }, GraphQL: &Rate{ Limit: 4, Remaining: 3, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, }, IntegrationManifest: &Rate{ Limit: 5, Remaining: 4, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, }, SourceImport: &Rate{ Limit: 6, Remaining: 5, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, }, CodeScanningUpload: &Rate{ Limit: 7, Remaining: 6, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, }, ActionsRunnerRegistration: &Rate{ Limit: 8, Remaining: 7, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, }, SCIM: &Rate{ Limit: 9, Remaining: 8, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, }, DependencySnapshots: &Rate{ Limit: 10, Remaining: 9, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 1, 0, time.UTC).Local()}, }, CodeSearch: &Rate{ Limit: 11, Remaining: 10, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()}, }, AuditLog: &Rate{ Limit: 12, Remaining: 11, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()}, }, } @@ -200,21 +211,22 @@ func TestRateLimits_overQuota(t *testing.T) { client.rateLimits[CoreCategory] = Rate{ Limit: 1, Remaining: 0, + Used: 1, Reset: Timestamp{time.Now().Add(time.Hour).Local()}, } mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, `{"resources":{ - "core": {"limit":2,"remaining":1,"reset":1372700873}, - "search": {"limit":3,"remaining":2,"reset":1372700874}, - "graphql": {"limit":4,"remaining":3,"reset":1372700875}, - "integration_manifest": {"limit":5,"remaining":4,"reset":1372700876}, - "source_import": {"limit":6,"remaining":5,"reset":1372700877}, - "code_scanning_upload": {"limit":7,"remaining":6,"reset":1372700878}, - "actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879}, - "scim": {"limit":9,"remaining":8,"reset":1372700880}, - "dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881}, - "code_search": {"limit":11,"remaining":10,"reset":1372700882}, - "audit_log": {"limit":12,"remaining":11,"reset":1372700883} + "core": {"limit":2,"remaining":1,"used":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"used":1,"reset":1372700874}, + "graphql": {"limit":4,"remaining":3,"used":1,"reset":1372700875}, + "integration_manifest": {"limit":5,"remaining":4,"used":1,"reset":1372700876}, + "source_import": {"limit":6,"remaining":5,"used":1,"reset":1372700877}, + "code_scanning_upload": {"limit":7,"remaining":6,"used":1,"reset":1372700878}, + "actions_runner_registration": {"limit":8,"remaining":7,"used":1,"reset":1372700879}, + "scim": {"limit":9,"remaining":8,"used":1,"reset":1372700880}, + "dependency_snapshots": {"limit":10,"remaining":9,"used":1,"reset":1372700881}, + "code_search": {"limit":11,"remaining":10,"used":1,"reset":1372700882}, + "audit_log": {"limit":12,"remaining":11,"used":1,"reset":1372700883} }}`) }) @@ -228,56 +240,67 @@ func TestRateLimits_overQuota(t *testing.T) { Core: &Rate{ Limit: 2, Remaining: 1, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, }, Search: &Rate{ Limit: 3, Remaining: 2, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, }, GraphQL: &Rate{ Limit: 4, Remaining: 3, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, }, IntegrationManifest: &Rate{ Limit: 5, Remaining: 4, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, }, SourceImport: &Rate{ Limit: 6, Remaining: 5, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, }, CodeScanningUpload: &Rate{ Limit: 7, Remaining: 6, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, }, ActionsRunnerRegistration: &Rate{ Limit: 8, Remaining: 7, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, }, SCIM: &Rate{ Limit: 9, Remaining: 8, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, }, DependencySnapshots: &Rate{ Limit: 10, Remaining: 9, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 1, 0, time.UTC).Local()}, }, CodeSearch: &Rate{ Limit: 11, Remaining: 10, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()}, }, AuditLog: &Rate{ Limit: 12, Remaining: 11, + Used: 1, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()}, }, } @@ -349,56 +372,67 @@ func TestRateLimits_Marshal(t *testing.T) { Core: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, Search: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, GraphQL: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, IntegrationManifest: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, SourceImport: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, CodeScanningUpload: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, ActionsRunnerRegistration: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, SCIM: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, DependencySnapshots: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, CodeSearch: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, AuditLog: &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, }, } @@ -407,56 +441,67 @@ func TestRateLimits_Marshal(t *testing.T) { "core": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "search": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "graphql": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "integration_manifest": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "source_import": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "code_scanning_upload": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "actions_runner_registration": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "scim": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "dependency_snapshots": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "code_search": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` }, "audit_log": { "limit": 1, "remaining": 1, + "used": 0, "reset": ` + referenceTimeStr + ` } }` @@ -471,13 +516,17 @@ func TestRate_Marshal(t *testing.T) { u := &Rate{ Limit: 1, Remaining: 1, + Used: 0, Reset: Timestamp{referenceTime}, + Resource: "core", } want := `{ "limit": 1, "remaining": 1, - "reset": ` + referenceTimeStr + ` + "used": 0, + "reset": ` + referenceTimeStr + `, + "resource": "core" }` testJSONMarshal(t, u, want)