Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: migueleliasweb/go-github-mock
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.0.20
Choose a base ref
...
head repository: migueleliasweb/go-github-mock
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.0.22
Choose a head ref
  • 8 commits
  • 10 files changed
  • 3 contributors

Commits on Oct 27, 2023

  1. upgrade go-github to v56

    go-github v56 among other changes removes the dependency on
    `github.com/ProtonMail/go-crypto`
    which drastically reduces transitive dependencies.
    pfiaux authored and migueleliasweb committed Oct 27, 2023
    Copy the full SHA
    ef74c7b View commit details
  2. Update GH definitions

    migueleliasweb committed Oct 27, 2023
    Copy the full SHA
    ebbb938 View commit details

Commits on Nov 3, 2023

  1. Copy the full SHA
    24b24ab View commit details
  2. Copy the full SHA
    dd18230 View commit details
  3. refactor: split out middleware for later re-use

    Also expose the burst parameter offered by `rate.NewLimiter`.
    jlucktay authored and migueleliasweb committed Nov 3, 2023
    Copy the full SHA
    abf52fe View commit details
  4. Copy the full SHA
    8a5c184 View commit details
  5. Copy the full SHA
    5771525 View commit details
  6. Copy the full SHA
    be5e2c5 View commit details
Showing with 230 additions and 81 deletions.
  1. +37 −20 README.md
  2. +2 −1 go.mod
  3. +4 −56 go.sum
  4. +70 −0 src/mock/endpointpattern.go
  5. +1 −1 src/mock/endpointpattern_test.go
  6. +33 −0 src/mock/server_options.go
  7. +80 −0 src/mock/server_options_external_test.go
  8. +1 −1 src/mock/server_options_test.go
  9. +1 −1 src/mock/server_test.go
  10. +1 −1 src/mock/utils.go
57 changes: 37 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,32 +3,28 @@

A library to aid unittesting code that uses Golang's Github SDK

# Installation
## Installation

```bash
go get github.com/migueleliasweb/go-github-mock
```

# Features
## Features

- Create mocks for successive calls for the same endpoint
- Pagination support
- Pagination support (see `mock.WithRequestMatchPages`)
- Mock error returns
- High level abstraction helps writing readabe unittests (see `mock.WithRequestMatch`)
- Lower level abstraction for advanced uses (see `mock.WithRequestMatchHandler`)
- Mock rate limiting errors from the api (see `mock.WithRateLimit`)

# Breaking changes
## Examples

- `v0.0.3` the API for the server options have beem simplified
- `v0.0.4` fixes to the gen script caused multiple url matches to change

# Example

```
```go
import "github.com/migueleliasweb/go-github-mock/src/mock"
```

## Multiple requests
### Multiple requests

```golang
mockedHTTPClient := mock.NewMockedHTTPClient(
@@ -87,7 +83,7 @@ projs, _, projsErr := c.Organizations.ListProjects(

```

## Returning empty results
### Returning empty results

```golang
mockedHTTPClient := NewMockedHTTPClient(
@@ -122,7 +118,7 @@ issues2, _, repo2Err := c.Issues.ListByRepo(ctx, "owner1", "repo2", &github.Issu
// repo2Err == nil
```

## Mocking errors from the API
### Mocking errors from the API

```golang
mockedHTTPClient := mock.NewMockedHTTPClient(
@@ -145,15 +141,15 @@ user, _, userErr := c.Users.Get(ctx, "someUser")

// user == nil

if userErr == nil {
if userErr == nil {
if ghErr, ok := userErr.(*github.ErrorResponse); ok {
fmt.Println(ghErr.Message) // == "github went belly up or something"
}
}

```

## Mocking with pagination
### Mocking with pagination

```golang
mockedHTTPClient := NewMockedHTTPClient(
@@ -214,7 +210,7 @@ for {
// len(allRepos) == 4
```

## Mocking for Github Enterprise
### Mocking for GitHub Enterprise

Github Enterprise uses a different prefix for its endpoints. In order to use the correct endpoints, please use the different set of `*Enterprise` options:

@@ -240,16 +236,37 @@ user, _, userErr := c.Users.Get(ctx, "myuser")
// user.Name == "foobar"
```

# Why
### Mocking with rate limits

`WithRateLimit` uses a single rate-limiting middleware across all endpoints on the mock router.

**NOTE:** This is an alpha feature. Future changes might break compatibility, until a stable version is released.

```go
mockedHTTPClient := mock.NewMockedHTTPClient(
mock.WithRequestMatchPages(
mock.GetOrgsReposByOrg,
[]github.Repository{{Name: github.String(repoOne)}},
[]github.Repository{{Name: github.String(repoTwo)}},
),

// The rate limiter will allow 10 requests per second, and a burst size of 1.
// These two options together mean that the rate of requests will be strictly enforced, so if any two requests are
// made less than 1/10th of a second apart, the latter will be refused and come back with a rate limit error.
mock.WithRateLimit(10, 1),
)
```

## Why

Some conversations got started on [go-github#1800](https://github.com/google/go-github/issues/1800) since `go-github` didn't provide an interface that could be easily reimplemented for unittests. After lots of conversations from the folks from [go-github](https://github.com/google/go-github) and quite a few PR ideas later, this style of testing was deemed not suitable to be part of the core SDK as it's not a feature of the API itself. Nonetheless, the ability of writing unittests for code that uses the `go-github` package is critical.
Some conversations got started on [go-github#1800](https://github.com/google/go-github/issues/1800) since `go-github` didn't provide an interface that could be easily reimplemented for unittests. After lots of conversations from the folks from [go-github](https://github.com/google/go-github) and quite a few PR ideas later, this style of testing was deemed not suitable to be part of the core SDK as it's not a feature of the API itself. Nonetheless, the ability of writing unittests for code that uses the `go-github` package is critical.

A reuseable, and not overly verbose, way of writing the tests was reached after some more interactions (months down the line) and here we are.

# Thanks
## Thanks

Thanks for all ideas and feedback from the folks in [go-github](https://github.com/google/go-github/).

# License
## License

This library is distributed under the MIT License found in LICENSE.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ go 1.16
require (
github.com/buger/jsonparser v1.1.1
github.com/go-kit/log v0.2.1
github.com/google/go-github/v55 v55.0.0
github.com/google/go-github/v56 v56.0.0
github.com/gorilla/mux v1.8.0
golang.org/x/time v0.3.0
)
60 changes: 4 additions & 56 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,70 +1,18 @@
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
github.com/google/go-github/v56 v56.0.0 h1:TysL7dMa/r7wsQi44BjqlwaHvwlFlqkK8CtBWCX3gb4=
github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
70 changes: 70 additions & 0 deletions src/mock/endpointpattern.go
Original file line number Diff line number Diff line change
@@ -1122,6 +1122,41 @@ var PostOrgsProjectsByOrg EndpointPattern = EndpointPattern{
Method: "POST",
}

var GetOrgsPropertiesSchemaByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/schema",
Method: "GET",
}

var PatchOrgsPropertiesSchemaByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/schema",
Method: "PATCH",
}

var GetOrgsPropertiesSchemaByOrgByCustomPropertyName EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/schema/{custom_property_name}",
Method: "GET",
}

var PutOrgsPropertiesSchemaByOrgByCustomPropertyName EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/schema/{custom_property_name}",
Method: "PUT",
}

var DeleteOrgsPropertiesSchemaByOrgByCustomPropertyName EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/schema/{custom_property_name}",
Method: "DELETE",
}

var GetOrgsPropertiesValuesByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/values",
Method: "GET",
}

var PatchOrgsPropertiesValuesByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/properties/values",
Method: "PATCH",
}

var GetOrgsPublicMembersByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/public_members",
Method: "GET",
@@ -1162,6 +1197,16 @@ var PostOrgsRulesetsByOrg EndpointPattern = EndpointPattern{
Method: "POST",
}

var GetOrgsRulesetsRuleSuitesByOrg EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/rulesets/rule-suites",
Method: "GET",
}

var GetOrgsRulesetsRuleSuitesByOrgByRuleSuiteId EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/rulesets/rule-suites/{rule_suite_id}",
Method: "GET",
}

var GetOrgsRulesetsByOrgByRulesetId EndpointPattern = EndpointPattern{
Pattern: "/orgs/{org}/rulesets/{ruleset_id}",
Method: "GET",
@@ -1742,6 +1787,11 @@ var PostReposActionsRunsDeploymentProtectionRuleByOwnerByRepoByRunId EndpointPat
Method: "POST",
}

var PostReposActionsRunsForceCancelByOwnerByRepoByRunId EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel",
Method: "POST",
}

var GetReposActionsRunsJobsByOwnerByRepoByRunId EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/actions/runs/{run_id}/jobs",
Method: "GET",
@@ -2242,6 +2292,11 @@ var GetReposCodespacesNewByOwnerByRepo EndpointPattern = EndpointPattern{
Method: "GET",
}

var GetReposCodespacesPermissionsCheckByOwnerByRepo EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/codespaces/permissions_check",
Method: "GET",
}

var GetReposCodespacesSecretsByOwnerByRepo EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/codespaces/secrets",
Method: "GET",
@@ -3112,6 +3167,11 @@ var PostReposProjectsByOwnerByRepo EndpointPattern = EndpointPattern{
Method: "POST",
}

var GetReposPropertiesValuesByOwnerByRepo EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/properties/values",
Method: "GET",
}

var GetReposPullsByOwnerByRepo EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/pulls",
Method: "GET",
@@ -3372,6 +3432,16 @@ var PostReposRulesetsByOwnerByRepo EndpointPattern = EndpointPattern{
Method: "POST",
}

var GetReposRulesetsRuleSuitesByOwnerByRepo EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/rulesets/rule-suites",
Method: "GET",
}

var GetReposRulesetsRuleSuitesByOwnerByRepoByRuleSuiteId EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}",
Method: "GET",
}

var GetReposRulesetsByOwnerByRepoByRulesetId EndpointPattern = EndpointPattern{
Pattern: "/repos/{owner}/{repo}/rulesets/{ruleset_id}",
Method: "GET",
2 changes: 1 addition & 1 deletion src/mock/endpointpattern_test.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import (
"context"
"testing"

"github.com/google/go-github/v55/github"
"github.com/google/go-github/v56/github"
)

func TestRepoGetContents(t *testing.T) {
33 changes: 33 additions & 0 deletions src/mock/server_options.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"net/http"

"github.com/gorilla/mux"
"golang.org/x/time/rate"
)

// WithRequestMatchHandler implements a request callback
@@ -132,3 +133,35 @@ func WithRequestMatchPagesEnterprise(

return WithRequestMatchPages(ep, pages...)
}

// rateLimitMiddleware enforces a rate limit using [golang.org/x/time/rate].
// rps is the number of requests per second allowed by the rate limiter.
// Higher burst values allow more calls to happen at once.
// A zero value for burst will not allow any events, unless rps == [rate.Inf].
// This middleware is intended to be used with [github.com/gorilla/mux].
func rateLimitMiddleware(rps float64, burst int) func(next http.Handler) http.Handler {
limiter := rate.NewLimiter(rate.Limit(rps), burst)

return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
// These values are based on this bit of logic within [github.com/google/go-github]:
// https://github.com/google/go-github/blob/5e25c5c215b3d21991d17447fba2e9d13a875159/github/github.go#L1243
w.Header().Set("X-RateLimit-Remaining", "0")
w.WriteHeader(http.StatusForbidden)

return
}

next.ServeHTTP(w, r)
})
}
}

// WithRateLimit enforces a rate limit on the mocked [http.Client].
// NOTE: This is an alpha feature. Future changes might break compatibility, until a stable version is released.
func WithRateLimit(rps float64, burst int) MockBackendOption {
return func(router *mux.Router) {
router.Use(rateLimitMiddleware(rps, burst))
}
}
80 changes: 80 additions & 0 deletions src/mock/server_options_external_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package mock_test

import (
"context"
"errors"
"testing"
"time"

"github.com/google/go-github/v56/github"
"github.com/migueleliasweb/go-github-mock/src/mock"
)

func TestWithRateLimit(t *testing.T) {
t.Parallel()

const (
repoOne = "repo-one"
repoTwo = "repo-two"
)

mhc := mock.NewMockedHTTPClient(
mock.WithRequestMatchPages(
mock.GetOrgsReposByOrg,
[]github.Repository{{Name: github.String(repoOne)}},
[]github.Repository{{Name: github.String(repoTwo)}},
),

// The rate limiter will allow 10 requests per second, and a burst size of 1.
// These two options together mean that the rate of requests will be strictly enforced, so if any two requests are
// made less than 1/10th of a second apart, the latter will be refused and come back with a rate limit error.
mock.WithRateLimit(10, 1),
)

ghc := github.NewClient(mhc)
opts := &github.RepositoryListByOrgOptions{}
repoNames := []string{}
rleCount := 0

for {
repos, resp, err := ghc.Repositories.ListByOrg(context.Background(), "org", opts)
if err != nil {
// The only type of error we expect is one from the rate limiter.
var rle *github.RateLimitError
if !errors.As(err, &rle) {
t.Fatalf("error is not a github.RateLimitError: %v", err)
}

rleCount++

// After hitting the rate limiter and getting the appropriate error above, sleeping for one tenth of a second
// should be enough to reset the limiter and try requesting the repo list again.
time.Sleep(time.Second / 10)

continue
}
defer resp.Body.Close()

if len(repos) != 1 {
t.Fatal("should receive one repo per page from mock")
}

// Save the returned repo name to our slice, to assert against later.
repoNames = append(repoNames, repos[0].GetName())

// Handle pagination.
if resp.NextPage == 0 {
break
}

opts.Page = resp.NextPage
}

if len(repoNames) != 2 || repoNames[0] != repoOne || repoNames[1] != repoTwo {
t.Fatalf("list of returned repo names is wrong: %v", repoNames)
}

if rleCount != 1 {
t.Fatal("did not receive the expected number of github.RateLimitError")
}
}
2 changes: 1 addition & 1 deletion src/mock/server_options_test.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ package mock
import (
"testing"

"github.com/google/go-github/v55/github"
"github.com/google/go-github/v56/github"
"github.com/gorilla/mux"
)

2 changes: 1 addition & 1 deletion src/mock/server_test.go
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import (
"strings"
"testing"

"github.com/google/go-github/v55/github"
"github.com/google/go-github/v56/github"
)

func TestNewMockedHTTPClient(t *testing.T) {
2 changes: 1 addition & 1 deletion src/mock/utils.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import (
"encoding/json"
"net/http"

"github.com/google/go-github/v55/github"
"github.com/google/go-github/v56/github"
)

// MustMarshal helper function that wraps json.Marshal