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
Add httpmock from cli/cli #28
Comments
I like this idea and I think we should do it. I already have migrated it to go-gh so it really just needs to be moved from the internal folder to export it. Before making it widely available there are a couple changes I would like to see to the interface while we still can:
These will require changes to the |
|
Great, I also had thoughts on defining a naming scheme for responders as well. I think our responders can be smarter than they currently are. In my opinion each should be able to handle both a string and a struct, if it is not a string we will try to marshal it to a JSON string. In this scenario we don't need to figure out a naming scheme for what takes in a string or a struct. I wrote up some code below on how I am envisioning the responders. Let me know what you think. func HTTPResponse(status int, header map[string][]string, body interface{}, cb func(*http.Request)) Responder {
return func(req *http.Request) (*http.Response, error) {
var b io.Reader
if s, ok := body.(string); ok {
b = bytes.NewBufferString(s)
} else {
s, _ := json.Marshal(body)
b = bytes.NewBuffer(s)
}
if cb != nil {
cb(req)
}
return httpResponse(status, header, b, req), nil
}
}
func RESTResponse(body interface{}, cb func(map[string]interface{})) Responder {
return func(req *http.Request) (*http.Response, error) {
var b io.Reader
if s, ok := body.(string); ok {
b = bytes.NewBufferString(s)
} else {
s, _ := json.Marshal(body)
b = bytes.NewBuffer(s)
}
bodyData := make(map[string]interface{})
err := decodeJSONBody(req, &bodyData)
if err != nil {
return nil, err
}
if cb != nil {
cb(bodyData)
}
return httpResponse(200, map[string][]string{}, b, req), nil
}
}
func GQLMutation(body interface{}, cb func(map[string]interface{})) Responder {
return func(req *http.Request) (*http.Response, error) {
var b io.Reader
if s, ok := body.(string); ok {
b = bytes.NewBufferString(s)
} else {
s, _ := json.Marshal(body)
b = bytes.NewBuffer(s)
}
var bodyData struct {
Variables struct {
Input map[string]interface{}
}
}
err := decodeJSONBody(req, &bodyData)
if err != nil {
return nil, err
}
if cb != nil {
cb(bodyData.Variables.Input)
}
return httpResponse(200, map[string][]string{}, b, req), nil
}
}
func GQLQuery(body interface{}, cb func(string, map[string]interface{})) Responder {
return func(req *http.Request) (*http.Response, error) {
var b io.Reader
if s, ok := body.(string); ok {
b = bytes.NewBufferString(s)
} else {
s, _ := json.Marshal(body)
b = bytes.NewBuffer(s)
}
var bodyData struct {
Query string
Variables map[string]interface{}
}
err := decodeJSONBody(req, &bodyData)
if err != nil {
return nil, err
}
if cb != nil {
cb(bodyData.Query, bodyData.Variables)
}
return httpResponse(200, map[string][]string{}, b, req), nil
}
}
func httpResponse(status int, header map[string][]string, body io.Reader, req *http.Request) *http.Response {
if _, ok := header["Content-Type"]; !ok {
header["Content-Type"] = []string{"application/json; charset=utf-8"}
}
return &http.Response{
StatusCode: status,
Header: header,
Body: io.NopCloser(body),
Request: req,
}
} |
Overall, I like it, but have a couple thoughts:
|
|
@heaths As part of the process of exporting |
@samcoe having had a change to use The main issue that is lacking is trying to match a request. With your Still, it'd be nice to have a way to not have to encode the entire query. Thoughts? |
@heaths You're right that matching on the entire query string would likely lead to brittle tests. Would it be possible to define a custom Request matcher with gock that extracts the GraphQL query name and matches on that? |
Seems one could using |
@heaths I am not sure that we necessarily need to include this functionality in func matchGQLRequest(query string, vars map[string]string) func(*http.Request, *gock.Request) (bool, error) {
return func(req *http.Request, ereq *gock.Request) (bool, error) {
var bodyData struct {
Query string
Variables map[string]string
}
bodyCopy := &bytes.Buffer{}
r := io.TeeReader(req.Body, bodyCopy)
req.Body = io.NopCloser(bodyCopy)
b, err := io.ReadAll(r)
if err != nil {
return false, err
}
err = json.Unmarshal(b, &bodyData)
if err != nil {
return false, err
}
if query != "" && query != bodyData.Query {
return false, nil
}
if len(vars) != 0 {
for k, v := range vars {
bv := bodyData.Variables[k]
if v != bv {
return false, nil
}
}
}
return true, nil
}
} It can be used like this: func TestGQLClient(t *testing.T) {
stubConfig(t, testConfig())
t.Cleanup(gock.Off)
gock.New("https://api.github.com").
Post("/graphql").
MatchHeader("Authorization", "token abc123").
AddMatcher(matchGQLRequest("QUERY", map[string]string{"var": "test"})).
Reply(200).
JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
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(), printPendingMocks(gock.Pending()))
assert.Equal(t, "hubot", res.Viewer.Login)
} |
It was just an idea to avoid everyone having to figure that out for themselves. Perhaps dubious, but a helper function in go-gh to make it more discoverable and easier for people to use seems like a good idea. I wasn't afraid I wouldn't figure something out, just that perhaps not everyone needs to do so as well. 🙂 |
The mock
Registry
and friends from https://github.com/cli/cli/tree/trunk/pkg/httpmock would be very useful for testing extensions.The text was updated successfully, but these errors were encountered: