diff --git a/github/github.go b/github/github.go index d71236ef08..edd86ab2ad 100644 --- a/github/github.go +++ b/github/github.go @@ -203,6 +203,7 @@ type Client struct { Organizations *OrganizationsService Projects *ProjectsService PullRequests *PullRequestsService + RateLimit *RateLimitService Reactions *ReactionsService Repositories *RepositoriesService SCIM *SCIMService @@ -424,6 +425,7 @@ func (c *Client) initialize() { c.Organizations = (*OrganizationsService)(&c.common) c.Projects = (*ProjectsService)(&c.common) c.PullRequests = (*PullRequestsService)(&c.common) + c.RateLimit = (*RateLimitService)(&c.common) c.Reactions = (*ReactionsService)(&c.common) c.Repositories = (*RepositoriesService)(&c.common) c.SCIM = (*SCIMService)(&c.common) @@ -1281,54 +1283,6 @@ func parseBoolResponse(err error) (bool, error) { return false, err } -// Rate represents the rate limit for the current client. -type Rate struct { - // The number of requests per hour the client is currently limited to. - Limit int `json:"limit"` - - // The number of remaining requests the client can make this hour. - Remaining int `json:"remaining"` - - // The time at which the current rate limit will reset. - Reset Timestamp `json:"reset"` -} - -func (r Rate) String() string { - return Stringify(r) -} - -// RateLimits represents the rate limits for the current client. -type RateLimits struct { - // The rate limit for non-search API requests. Unauthenticated - // requests are limited to 60 per hour. Authenticated requests are - // limited to 5,000 per hour. - // - // GitHub API docs: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting - Core *Rate `json:"core"` - - // The rate limit for search API requests. Unauthenticated requests - // are limited to 10 requests per minutes. Authenticated requests are - // limited to 30 per minute. - // - // GitHub API docs: https://docs.github.com/en/rest/search#rate-limit - Search *Rate `json:"search"` - - // GitHub API docs: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit - GraphQL *Rate `json:"graphql"` - - // GitHub API dos: https://docs.github.com/en/rest/rate-limit - IntegrationManifest *Rate `json:"integration_manifest"` - - SourceImport *Rate `json:"source_import"` - CodeScanningUpload *Rate `json:"code_scanning_upload"` - ActionsRunnerRegistration *Rate `json:"actions_runner_registration"` - SCIM *Rate `json:"scim"` -} - -func (r RateLimits) String() string { - return Stringify(r) -} - type rateLimitCategory uint8 const ( @@ -1378,53 +1332,10 @@ func category(method, path string) rateLimitCategory { } // RateLimits returns the rate limits for the current client. +// +// Deprecated: Use RateLimitService.Get instead. func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) { - req, err := c.NewRequest("GET", "rate_limit", nil) - if err != nil { - return nil, nil, err - } - - response := new(struct { - Resources *RateLimits `json:"resources"` - }) - - // This resource is not subject to rate limits. - ctx = context.WithValue(ctx, bypassRateLimitCheck, true) - resp, err := c.Do(ctx, req, response) - if err != nil { - return nil, resp, err - } - - if response.Resources != nil { - c.rateMu.Lock() - if response.Resources.Core != nil { - c.rateLimits[coreCategory] = *response.Resources.Core - } - if response.Resources.Search != nil { - c.rateLimits[searchCategory] = *response.Resources.Search - } - if response.Resources.GraphQL != nil { - c.rateLimits[graphqlCategory] = *response.Resources.GraphQL - } - if response.Resources.IntegrationManifest != nil { - c.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest - } - if response.Resources.SourceImport != nil { - c.rateLimits[sourceImportCategory] = *response.Resources.SourceImport - } - if response.Resources.CodeScanningUpload != nil { - c.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload - } - if response.Resources.ActionsRunnerRegistration != nil { - c.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration - } - if response.Resources.SCIM != nil { - c.rateLimits[scimCategory] = *response.Resources.SCIM - } - c.rateMu.Unlock() - } - - return response.Resources, resp, nil + return c.RateLimit.Get(ctx) } func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request { diff --git a/github/github_test.go b/github/github_test.go index 7d92946dfd..90e95b035f 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -468,23 +468,6 @@ func TestClient_rateLimits(t *testing.T) { } } -func TestRateLimits_String(t *testing.T) { - v := RateLimits{ - Core: &Rate{}, - Search: &Rate{}, - GraphQL: &Rate{}, - IntegrationManifest: &Rate{}, - SourceImport: &Rate{}, - CodeScanningUpload: &Rate{}, - ActionsRunnerRegistration: &Rate{}, - SCIM: &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}}}` - if got := v.String(); got != want { - t.Errorf("RateLimits.String = %v, want %v", got, want) - } -} - func TestNewRequest(t *testing.T) { c := NewClient(nil) @@ -2123,251 +2106,6 @@ func TestError_Error(t *testing.T) { } } -func TestRateLimits(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - 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} - }}`) - }) - - ctx := context.Background() - rate, _, err := client.RateLimits(ctx) - if err != nil { - t.Errorf("RateLimits returned error: %v", err) - } - - want := &RateLimits{ - Core: &Rate{ - Limit: 2, - Remaining: 1, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, - }, - Search: &Rate{ - Limit: 3, - Remaining: 2, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, - }, - GraphQL: &Rate{ - Limit: 4, - Remaining: 3, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, - }, - IntegrationManifest: &Rate{ - Limit: 5, - Remaining: 4, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, - }, - SourceImport: &Rate{ - Limit: 6, - Remaining: 5, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, - }, - CodeScanningUpload: &Rate{ - Limit: 7, - Remaining: 6, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, - }, - ActionsRunnerRegistration: &Rate{ - Limit: 8, - Remaining: 7, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, - }, - SCIM: &Rate{ - Limit: 9, - Remaining: 8, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, - }, - } - if !cmp.Equal(rate, want) { - t.Errorf("RateLimits returned %+v, want %+v", rate, want) - } - tests := []struct { - category rateLimitCategory - rate *Rate - }{ - { - category: coreCategory, - rate: want.Core, - }, - { - category: searchCategory, - rate: want.Search, - }, - { - category: graphqlCategory, - rate: want.GraphQL, - }, - { - category: integrationManifestCategory, - rate: want.IntegrationManifest, - }, - { - category: sourceImportCategory, - rate: want.SourceImport, - }, - { - category: codeScanningUploadCategory, - rate: want.CodeScanningUpload, - }, - { - category: actionsRunnerRegistrationCategory, - rate: want.ActionsRunnerRegistration, - }, - { - category: scimCategory, - rate: want.SCIM, - }, - } - - for _, tt := range tests { - if got, want := client.rateLimits[tt.category], *tt.rate; got != want { - t.Errorf("client.rateLimits[%v] is %+v, want %+v", tt.category, got, want) - } - } -} - -func TestRateLimits_coverage(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - ctx := context.Background() - - const methodName = "RateLimits" - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - _, resp, err := client.RateLimits(ctx) - return resp, err - }) -} - -func TestRateLimits_overQuota(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - client.rateLimits[coreCategory] = Rate{ - Limit: 1, - Remaining: 0, - 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} - }}`) - }) - - ctx := context.Background() - rate, _, err := client.RateLimits(ctx) - if err != nil { - t.Errorf("RateLimits returned error: %v", err) - } - - want := &RateLimits{ - Core: &Rate{ - Limit: 2, - Remaining: 1, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, - }, - Search: &Rate{ - Limit: 3, - Remaining: 2, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, - }, - GraphQL: &Rate{ - Limit: 4, - Remaining: 3, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, - }, - IntegrationManifest: &Rate{ - Limit: 5, - Remaining: 4, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, - }, - SourceImport: &Rate{ - Limit: 6, - Remaining: 5, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, - }, - CodeScanningUpload: &Rate{ - Limit: 7, - Remaining: 6, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, - }, - ActionsRunnerRegistration: &Rate{ - Limit: 8, - Remaining: 7, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, - }, - SCIM: &Rate{ - Limit: 9, - Remaining: 8, - Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, - }, - } - if !cmp.Equal(rate, want) { - t.Errorf("RateLimits returned %+v, want %+v", rate, want) - } - - tests := []struct { - category rateLimitCategory - rate *Rate - }{ - { - category: coreCategory, - rate: want.Core, - }, - { - category: searchCategory, - rate: want.Search, - }, - { - category: graphqlCategory, - rate: want.GraphQL, - }, - { - category: integrationManifestCategory, - rate: want.IntegrationManifest, - }, - { - category: sourceImportCategory, - rate: want.SourceImport, - }, - { - category: codeScanningUploadCategory, - rate: want.CodeScanningUpload, - }, - { - category: actionsRunnerRegistrationCategory, - rate: want.ActionsRunnerRegistration, - }, - { - category: scimCategory, - rate: want.SCIM, - }, - } - for _, tt := range tests { - if got, want := client.rateLimits[tt.category], *tt.rate; got != want { - t.Errorf("client.rateLimits[%v] is %+v, want %+v", tt.category, got, want) - } - } -} - func TestSetCredentialsAsHeaders(t *testing.T) { req := new(http.Request) id, secret := "id", "secret" @@ -2770,116 +2508,6 @@ func TestError_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } -func TestRate_Marshal(t *testing.T) { - testJSONMarshal(t, &Rate{}, "{}") - - u := &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - } - - want := `{ - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }` - - testJSONMarshal(t, u, want) -} - -func TestRateLimits_Marshal(t *testing.T) { - testJSONMarshal(t, &RateLimits{}, "{}") - - u := &RateLimits{ - Core: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - Search: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - GraphQL: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - IntegrationManifest: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - SourceImport: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - CodeScanningUpload: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - ActionsRunnerRegistration: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - SCIM: &Rate{ - Limit: 1, - Remaining: 1, - Reset: Timestamp{referenceTime}, - }, - } - - want := `{ - "core": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "search": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "graphql": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "integration_manifest": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "source_import": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "code_scanning_upload": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "actions_runner_registration": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - }, - "scim": { - "limit": 1, - "remaining": 1, - "reset": ` + referenceTimeStr + ` - } - }` - - testJSONMarshal(t, u, want) -} - func TestParseTokenExpiration(t *testing.T) { tests := []struct { header string diff --git a/github/rate_limit.go b/github/rate_limit.go new file mode 100644 index 0000000000..4243a14403 --- /dev/null +++ b/github/rate_limit.go @@ -0,0 +1,109 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "context" + +// RateLimitService provides access to rate limit functions in the GitHub API. +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. + Limit int `json:"limit"` + + // The number of remaining requests the client can make this hour. + Remaining int `json:"remaining"` + + // The time at which the current rate limit will reset. + Reset Timestamp `json:"reset"` +} + +func (r Rate) String() string { + return Stringify(r) +} + +// RateLimits represents the rate limits for the current client. +type RateLimits struct { + // The rate limit for non-search API requests. Unauthenticated + // requests are limited to 60 per hour. Authenticated requests are + // limited to 5,000 per hour. + // + // GitHub API docs: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting + Core *Rate `json:"core"` + + // The rate limit for search API requests. Unauthenticated requests + // are limited to 10 requests per minutes. Authenticated requests are + // limited to 30 per minute. + // + // GitHub API docs: https://docs.github.com/en/rest/search#rate-limit + Search *Rate `json:"search"` + + // GitHub API docs: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit + GraphQL *Rate `json:"graphql"` + + // GitHub API dos: https://docs.github.com/en/rest/rate-limit + IntegrationManifest *Rate `json:"integration_manifest"` + + SourceImport *Rate `json:"source_import"` + CodeScanningUpload *Rate `json:"code_scanning_upload"` + ActionsRunnerRegistration *Rate `json:"actions_runner_registration"` + SCIM *Rate `json:"scim"` +} + +func (r RateLimits) String() string { + return Stringify(r) +} + +// RateLimits returns the rate limits for the current client. +func (s *RateLimitService) Get(ctx context.Context) (*RateLimits, *Response, error) { + req, err := s.client.NewRequest("GET", "rate_limit", nil) + if err != nil { + return nil, nil, err + } + + response := new(struct { + Resources *RateLimits `json:"resources"` + }) + + // This resource is not subject to rate limits. + ctx = context.WithValue(ctx, bypassRateLimitCheck, true) + resp, err := s.client.Do(ctx, req, response) + if err != nil { + return nil, resp, err + } + + if response.Resources != nil { + s.client.rateMu.Lock() + if response.Resources.Core != nil { + s.client.rateLimits[coreCategory] = *response.Resources.Core + } + if response.Resources.Search != nil { + s.client.rateLimits[searchCategory] = *response.Resources.Search + } + if response.Resources.GraphQL != nil { + s.client.rateLimits[graphqlCategory] = *response.Resources.GraphQL + } + if response.Resources.IntegrationManifest != nil { + s.client.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest + } + if response.Resources.SourceImport != nil { + s.client.rateLimits[sourceImportCategory] = *response.Resources.SourceImport + } + if response.Resources.CodeScanningUpload != nil { + s.client.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload + } + if response.Resources.ActionsRunnerRegistration != nil { + s.client.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration + } + if response.Resources.SCIM != nil { + s.client.rateLimits[scimCategory] = *response.Resources.SCIM + } + s.client.rateMu.Unlock() + } + + return response.Resources, resp, nil +} diff --git a/github/rate_limit_test.go b/github/rate_limit_test.go new file mode 100644 index 0000000000..167288bc88 --- /dev/null +++ b/github/rate_limit_test.go @@ -0,0 +1,388 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestRateLimits_String(t *testing.T) { + v := RateLimits{ + Core: &Rate{}, + Search: &Rate{}, + GraphQL: &Rate{}, + IntegrationManifest: &Rate{}, + SourceImport: &Rate{}, + CodeScanningUpload: &Rate{}, + ActionsRunnerRegistration: &Rate{}, + SCIM: &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}}}` + if got := v.String(); got != want { + t.Errorf("RateLimits.String = %v, want %v", got, want) + } +} + +func TestRateLimits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + 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} + }}`) + }) + + ctx := context.Background() + rate, _, err := client.RateLimit.Get(ctx) + if err != nil { + t.Errorf("RateLimits returned error: %v", err) + } + + want := &RateLimits{ + Core: &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, + }, + Search: &Rate{ + Limit: 3, + Remaining: 2, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, + }, + GraphQL: &Rate{ + Limit: 4, + Remaining: 3, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, + }, + IntegrationManifest: &Rate{ + Limit: 5, + Remaining: 4, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, + }, + SourceImport: &Rate{ + Limit: 6, + Remaining: 5, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, + }, + CodeScanningUpload: &Rate{ + Limit: 7, + Remaining: 6, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, + }, + ActionsRunnerRegistration: &Rate{ + Limit: 8, + Remaining: 7, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, + }, + SCIM: &Rate{ + Limit: 9, + Remaining: 8, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, + }, + } + if !cmp.Equal(rate, want) { + t.Errorf("RateLimits returned %+v, want %+v", rate, want) + } + tests := []struct { + category rateLimitCategory + rate *Rate + }{ + { + category: coreCategory, + rate: want.Core, + }, + { + category: searchCategory, + rate: want.Search, + }, + { + category: graphqlCategory, + rate: want.GraphQL, + }, + { + category: integrationManifestCategory, + rate: want.IntegrationManifest, + }, + { + category: sourceImportCategory, + rate: want.SourceImport, + }, + { + category: codeScanningUploadCategory, + rate: want.CodeScanningUpload, + }, + { + category: actionsRunnerRegistrationCategory, + rate: want.ActionsRunnerRegistration, + }, + { + category: scimCategory, + rate: want.SCIM, + }, + } + + for _, tt := range tests { + if got, want := client.rateLimits[tt.category], *tt.rate; got != want { + t.Errorf("client.rateLimits[%v] is %+v, want %+v", tt.category, got, want) + } + } +} + +func TestRateLimits_coverage(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + ctx := context.Background() + + const methodName = "RateLimits" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + _, resp, err := client.RateLimit.Get(ctx) + return resp, err + }) +} + +func TestRateLimits_overQuota(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + client.rateLimits[coreCategory] = Rate{ + Limit: 1, + Remaining: 0, + 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} + }}`) + }) + + ctx := context.Background() + rate, _, err := client.RateLimit.Get(ctx) + if err != nil { + t.Errorf("RateLimits returned error: %v", err) + } + + want := &RateLimits{ + Core: &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, + }, + Search: &Rate{ + Limit: 3, + Remaining: 2, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, + }, + GraphQL: &Rate{ + Limit: 4, + Remaining: 3, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 55, 0, time.UTC).Local()}, + }, + IntegrationManifest: &Rate{ + Limit: 5, + Remaining: 4, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 56, 0, time.UTC).Local()}, + }, + SourceImport: &Rate{ + Limit: 6, + Remaining: 5, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 57, 0, time.UTC).Local()}, + }, + CodeScanningUpload: &Rate{ + Limit: 7, + Remaining: 6, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 58, 0, time.UTC).Local()}, + }, + ActionsRunnerRegistration: &Rate{ + Limit: 8, + Remaining: 7, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 59, 0, time.UTC).Local()}, + }, + SCIM: &Rate{ + Limit: 9, + Remaining: 8, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 00, 0, time.UTC).Local()}, + }, + } + if !cmp.Equal(rate, want) { + t.Errorf("RateLimits returned %+v, want %+v", rate, want) + } + + tests := []struct { + category rateLimitCategory + rate *Rate + }{ + { + category: coreCategory, + rate: want.Core, + }, + { + category: searchCategory, + rate: want.Search, + }, + { + category: graphqlCategory, + rate: want.GraphQL, + }, + { + category: integrationManifestCategory, + rate: want.IntegrationManifest, + }, + { + category: sourceImportCategory, + rate: want.SourceImport, + }, + { + category: codeScanningUploadCategory, + rate: want.CodeScanningUpload, + }, + { + category: actionsRunnerRegistrationCategory, + rate: want.ActionsRunnerRegistration, + }, + { + category: scimCategory, + rate: want.SCIM, + }, + } + for _, tt := range tests { + if got, want := client.rateLimits[tt.category], *tt.rate; got != want { + t.Errorf("client.rateLimits[%v] is %+v, want %+v", tt.category, got, want) + } + } +} + +func TestRateLimits_Marshal(t *testing.T) { + testJSONMarshal(t, &RateLimits{}, "{}") + + u := &RateLimits{ + Core: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + Search: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + GraphQL: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + IntegrationManifest: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + SourceImport: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + CodeScanningUpload: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + ActionsRunnerRegistration: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + SCIM: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, + } + + want := `{ + "core": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "search": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "graphql": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "integration_manifest": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "source_import": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "code_scanning_upload": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "actions_runner_registration": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }, + "scim": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + } + }` + + testJSONMarshal(t, u, want) +} + +func TestRate_Marshal(t *testing.T) { + testJSONMarshal(t, &Rate{}, "{}") + + u := &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + } + + want := `{ + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, u, want) +} diff --git a/test/integration/misc_test.go b/test/integration/misc_test.go index 6ffb163fdc..0e9ef6e01e 100644 --- a/test/integration/misc_test.go +++ b/test/integration/misc_test.go @@ -49,7 +49,7 @@ func TestAPIMeta(t *testing.T) { } func TestRateLimits(t *testing.T) { - limits, _, err := client.RateLimits(context.Background()) + limits, _, err := client.RateLimit.Get(context.Background()) if err != nil { t.Fatalf("RateLimits returned error: %v", err) }