Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate from httpmock to gock #31

Merged
merged 3 commits into from May 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 26 additions & 25 deletions gh_test.go
Expand Up @@ -6,9 +6,9 @@ import (
"testing"

"github.com/cli/go-gh/internal/config"
"github.com/cli/go-gh/internal/httpmock"
"github.com/cli/go-gh/pkg/api"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)

func TestHelperProcess(t *testing.T) {
Expand Down Expand Up @@ -47,6 +47,7 @@ func TestRunError(t *testing.T) {
}

func TestRESTClient(t *testing.T) {
t.Cleanup(gock.Off)
tempDir := t.TempDir()
orig_GH_CONFIG_DIR := os.Getenv("GH_CONFIG_DIR")
orig_GH_TOKEN := os.Getenv("GH_TOKEN")
Expand All @@ -57,24 +58,24 @@ func TestRESTClient(t *testing.T) {
os.Setenv("GH_CONFIG_DIR", tempDir)
os.Setenv("GH_TOKEN", "GH_TOKEN")

http := httpmock.NewRegistry(t)
http.Register(
httpmock.REST("GET", "some/test/path"),
httpmock.StatusStringResponse(200, `{"message": "success"}`),
)
gock.New("https://api.github.com").
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of gock.New(), could we use a gock initialization method that doesn't intercept the default http.DefaultTransport, but makes a new transport that we can pass via options to RESTClient()? That way it's more clear what's being initialized, there is no reliance to global state, and we don't have to do t.Cleanup(gock.Off())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we could get around this but this check functionality deep in the gock implementation makes it infeasible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see; it looks like the library is designed in a way that it always wants to be used with global intercept. 👍

Get("/some/test/path").
MatchHeader("Authorization", "token GH_TOKEN").
Reply(200).
JSON(`{"message": "success"}`)

client, err := RESTClient(&api.ClientOptions{Transport: http})
client, err := RESTClient(nil)
assert.NoError(t, err)

res := struct{ Message string }{}
err = client.Do("GET", "some/test/path", nil, &res)
assert.NoError(t, err)
assert.True(t, gock.IsDone())
assert.Equal(t, "success", res.Message)
assert.Equal(t, "api.github.com", http.Requests[0].URL.Hostname())
assert.Equal(t, "token GH_TOKEN", http.Requests[0].Header.Get("Authorization"))
}

func TestGQLClient(t *testing.T) {
t.Cleanup(gock.Off)
tempDir := t.TempDir()
orig_GH_CONFIG_DIR := os.Getenv("GH_CONFIG_DIR")
orig_GH_TOKEN := os.Getenv("GH_TOKEN")
Expand All @@ -85,25 +86,26 @@ func TestGQLClient(t *testing.T) {
os.Setenv("GH_CONFIG_DIR", tempDir)
os.Setenv("GH_TOKEN", "GH_TOKEN")

http := httpmock.NewRegistry(t)
http.Register(
httpmock.GQL("QUERY"),
httpmock.StringResponse(`{"data":{"viewer":{"login":"hubot"}}}`),
)
gock.New("https://api.github.com").
Post("/graphql").
MatchHeader("Authorization", "token GH_TOKEN").
BodyString("QUERY").
samcoe marked this conversation as resolved.
Show resolved Hide resolved
Reply(200).
JSON(`{"data":{"viewer":{"login":"hubot"}}}`)

client, err := GQLClient(&api.ClientOptions{Transport: http})
client, err := GQLClient(nil)
assert.NoError(t, err)

vars := map[string]interface{}{"var": "test"}
res := struct{ Viewer struct{ Login string } }{}
err = client.Do("QUERY", vars, &res)
assert.NoError(t, err)
assert.True(t, gock.IsDone())
samcoe marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, "hubot", res.Viewer.Login)
assert.Equal(t, "api.github.com", http.Requests[0].URL.Hostname())
assert.Equal(t, "token GH_TOKEN", http.Requests[0].Header.Get("Authorization"))
}

func TestHTTPClient(t *testing.T) {
t.Cleanup(gock.Off)
tempDir := t.TempDir()
orig_GH_CONFIG_DIR := os.Getenv("GH_CONFIG_DIR")
orig_GH_TOKEN := os.Getenv("GH_TOKEN")
Expand All @@ -114,20 +116,19 @@ func TestHTTPClient(t *testing.T) {
os.Setenv("GH_CONFIG_DIR", tempDir)
os.Setenv("GH_TOKEN", "GH_TOKEN")

http := httpmock.NewRegistry(t)
http.Register(
httpmock.REST("GET", "some/test/path"),
httpmock.StatusStringResponse(200, `{"message": "success"}`),
)
gock.New("https://api.github.com").
Get("/some/test/path").
MatchHeader("Authorization", "token GH_TOKEN").
Reply(200).
JSON(`{"message": "success"}`)

client, err := HTTPClient(&api.ClientOptions{Transport: http})
client, err := HTTPClient(nil)
assert.NoError(t, err)

res, err := client.Get("https://api.github.com/some/test/path")
assert.NoError(t, err)
assert.True(t, gock.IsDone())
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, "api.github.com", http.Requests[0].URL.Hostname())
assert.Equal(t, "token GH_TOKEN", http.Requests[0].Header.Get("Authorization"))
}

func TestResolveOptions(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -10,5 +10,6 @@ require (
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.7.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/h2non/gock.v1 v1.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
5 changes: 5 additions & 0 deletions go.sum
Expand Up @@ -6,13 +6,16 @@ github.com/cli/shurcooL-graphql v0.0.1 h1:/9J3t9O6p1B8zdBBtQighq5g7DQRItBwuwGh3S
github.com/cli/shurcooL-graphql v0.0.1/go.mod h1:U7gCSuMZP/Qy7kbqkk5PrqXEeDgtfG5K+W+u8weorps=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs=
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -28,6 +31,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
89 changes: 59 additions & 30 deletions internal/api/gql_client_test.go
Expand Up @@ -3,83 +3,112 @@ package api
import (
"testing"

"github.com/cli/go-gh/internal/httpmock"
"github.com/cli/go-gh/pkg/api"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)

func TestGQLClientDo(t *testing.T) {
tests := []struct {
name string
host string
matcher httpmock.Matcher
responder httpmock.Responder
httpMocks func()
wantErr bool
wantErrMsg string
wantHost string
wantLogin string
}{
{
name: "success request",
responder: httpmock.StringResponse(`{"data":{"viewer":{"login":"hubot"}}}`),
name: "success request",
httpMocks: func() {
gock.New("https://api.github.com").
Post("/graphql").
BodyString("QUERY").
Reply(200).
JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
},
wantLogin: "hubot",
},
{
name: "fail request",
responder: httpmock.StringResponse(`{"errors":[{"message":"OH NO"},{"message":"this is fine"}]}`),
name: "fail request",
httpMocks: func() {
gock.New("https://api.github.com").
Post("/graphql").
BodyString("QUERY").
Reply(200).
JSON(`{"errors":[{"message":"OH NO"},{"message":"this is fine"}]}`)
},
wantErr: true,
wantErrMsg: "GQL error: OH NO\nthis is fine",
},
{
name: "http fail request empty response",
responder: httpmock.StatusStringResponse(404, `{}`),
name: "http fail request empty response",
httpMocks: func() {
gock.New("https://api.github.com").
Post("/graphql").
BodyString("QUERY").
Reply(404).
JSON(`{}`)
},
wantErr: true,
wantErrMsg: "HTTP 404 (https://api.github.com/graphql)",
},
{
name: "http fail request message response",
responder: httpmock.StatusStringResponse(422, `{"message": "OH NO"}`),
name: "http fail request message response",
httpMocks: func() {
gock.New("https://api.github.com").
Post("/graphql").
BodyString("QUERY").
Reply(422).
JSON(`{"message": "OH NO"}`)
},
wantErr: true,
wantErrMsg: "HTTP 422: OH NO (https://api.github.com/graphql)",
},
{
name: "http fail request errors response",
responder: httpmock.StatusStringResponse(502, `{"errors":[{"message":"Something went wrong"}]}`),
name: "http fail request errors response",
httpMocks: func() {
gock.New("https://api.github.com").
Post("/graphql").
BodyString("QUERY").
Reply(502).
JSON(`{"errors":[{"message":"Something went wrong"}]}`)
},
wantErr: true,
wantErrMsg: "HTTP 502: Something went wrong (https://api.github.com/graphql)",
},
{
name: "support enterprise hosts",
responder: httpmock.StatusStringResponse(204, "{}"),
host: "enterprise.com",
wantHost: "enterprise.com",
name: "support enterprise hosts",
host: "enterprise.com",
httpMocks: func() {
gock.New("https://enterprise.com").
Post("/api/graphql").
BodyString("QUERY").
Reply(200).
JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
},
wantLogin: "hubot",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Cleanup(gock.Off)
if tt.host == "" {
tt.host = "github.com"
}
if tt.wantHost == "" {
tt.wantHost = "api.github.com"
if tt.httpMocks != nil {
tt.httpMocks()
}
http := httpmock.NewRegistry(t)
client := NewGQLClient(tt.host, &api.ClientOptions{Transport: http})
http.Register(
httpmock.GQL("QUERY"),
tt.responder,
)
client := NewGQLClient(tt.host, nil)
vars := map[string]interface{}{"var": "test"}
res := struct{ Viewer struct{ Login string } }{}
err := client.Do("QUERY", vars, &res)
if tt.wantErr {
assert.EqualError(t, err, tt.wantErrMsg)
return
} else {
assert.NoError(t, err)
}
assert.NoError(t, err)
assert.True(t, gock.IsDone())
assert.Equal(t, tt.wantLogin, res.Viewer.Login)
assert.Equal(t, tt.wantHost, http.Requests[0].URL.Hostname())
})
}
}