Skip to content

Mock transport with pc frames #2008

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions mock/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2021 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 mock

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"runtime"
"strings"
)

type RequestMatch = string
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.


func MatchIncomingRequest(r *http.Request, rm RequestMatch) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.

pc := make([]uintptr, 100)
n := runtime.Callers(0, pc)
if n == 0 {
return false
}

pc = pc[:n]
frames := runtime.CallersFrames(pc)

for {
frame, more := frames.Next()

if strings.Contains(frame.File, "go-github") &&
strings.Contains(frame.Function, "github.") &&
strings.Contains(frame.Function, "Service") {
splitFuncName := strings.Split(frame.Function, "/")
methodCall := splitFuncName[len(splitFuncName)-1]

if methodCall == rm {
return true
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about this?

		if strings.Contains(frame.File, "go-github") &&
			strings.Contains(frame.Function, "github.") &&
			strings.Contains(frame.Function, "Service") &&
			strings.HasSuffix(frame.Function, "/"+rm) {
				return true
			}

If you don't want "/"+rm within the for loop (although I'm betting that the compiler would optimize this such that it is not an issue),
you could add something like this before the for loop:

if !strings.HasPrefix(rm, "/") {
	rm = "/" + rm
}

}
}

if !more {
return false
}
}
}

type MockRoundTripper struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.
Also, please describe what the [][]byte is used for.

RequestMocks map[RequestMatch][][]byte
}

// RoundTrip implements http.RoundTripper interface
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// RoundTrip implements http.RoundTripper interface
// RoundTrip implements http.RoundTripper interface.

func (mrt *MockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
for requestMatch, respBodies := range mrt.RequestMocks {
if MatchIncomingRequest(r, requestMatch) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if MatchIncomingRequest(r, requestMatch) {
if !MatchIncomingRequest(r, requestMatch) {
continue
}

if len(respBodies) == 0 {
fmt.Printf(
"no more available mocked responses for endpoit %s\n",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"no more available mocked responses for endpoit %s\n",
"no more available mocked responses for endpoint %q\n",

r.URL.Path,
)

fmt.Println("please add the required RequestMatch to the MockHttpClient. Eg.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
fmt.Println("please add the required RequestMatch to the MockHttpClient. Eg.")
fmt.Println("please add the required RequestMatch to the MockHTTPClient, e.g.:")

fmt.Println(`
mockedHttpClient := NewMockHttpClient(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
mockedHttpClient := NewMockHttpClient(
mockedHTTPClient := NewMockHTTPClient(

WithRequestMatch(
RequestMatchUsersGet,
MustMarshall(github.User{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
MustMarshall(github.User{
MustMarshal(github.User{

Name: github.String("foobar"),
}),
),
WithRequestMatch(
RequestMatchOrganizationsList,
MustMarshall([]github.Organization{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
MustMarshall([]github.Organization{
MustMarshal([]github.Organization{

{
Name: github.String("foobar123"),
},
}),
),
)
`)

panic(nil)
}

resp := respBodies[0]

defer func(mrt *MockRoundTripper, rm RequestMatch) {
mrt.RequestMocks[rm] = mrt.RequestMocks[rm][1:]
}(mrt, requestMatch)

re := bytes.NewReader(resp)

return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(re),
}, nil
}
}

return nil, fmt.Errorf(
"couldn find a mock request that matches the request sent to: %s",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"couldn find a mock request that matches the request sent to: %s",
"couldn't find a mock request that matches the request sent to: %q",

r.URL.Path,
)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please delete this blank line.

}

var _ http.RoundTripper = &MockRoundTripper{}

type MockHttpClientOption func(*MockRoundTripper)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.

Suggested change
type MockHttpClientOption func(*MockRoundTripper)
type MockHTTPClientOption func(*MockRoundTripper)


func WithRequestMatch(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.

rm RequestMatch,
marshalled []byte,
) MockHttpClientOption {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
) MockHttpClientOption {
) MockHTTPClientOption {

return func(mrt *MockRoundTripper) {
if _, found := mrt.RequestMocks[rm]; !found {
mrt.RequestMocks[rm] = make([][]byte, 0)
}

mrt.RequestMocks[rm] = append(
mrt.RequestMocks[rm],
marshalled,
)
}
}

func NewMockHttpClient(options ...MockHttpClientOption) *http.Client {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add godoc comment.

Suggested change
func NewMockHttpClient(options ...MockHttpClientOption) *http.Client {
func NewMockHTTPClient(options ...MockHTTPClientOption) *http.Client {

rt := &MockRoundTripper{
RequestMocks: make(map[RequestMatch][][]byte),
}

for _, o := range options {
o(rt)
}

return &http.Client{
Transport: rt,
}
}

func MustMarshal(v interface{}) []byte {
b, err := json.Marshal(v)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove this blank line.

if err == nil {
return b
}

panic(err)
}
64 changes: 64 additions & 0 deletions mock/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2021 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 mock

import (
"context"
"testing"

"github.com/google/go-github/v37/github"
)

func TestMockClient(t *testing.T) {
ctx := context.Background()

mockedHttpClient := NewMockHttpClient(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
mockedHttpClient := NewMockHttpClient(
mockedHTTPClient := NewMockHTTPClient(

WithRequestMatch(
RequestMatchUsersGet,
MustMarshal(github.User{
Name: github.String("foobar"),
}),
),
WithRequestMatch(
RequestMatchOrganizationsList,
MustMarshal([]github.Organization{
{
Name: github.String("foobar123thisorgwasmocked"),
},
}),
),
)

c := github.NewClient(mockedHttpClient)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
c := github.NewClient(mockedHttpClient)
c := github.NewClient(mockedHTTPClient)


user, _, userErr := c.Users.Get(ctx, "someUser")

if user == nil || user.Name == nil || *user.Name != "foobar" {
t.Fatalf("User name is %s, want foobar", user)
}

if userErr != nil {
t.Errorf("User err is %s, want nil", userErr.Error())
}

orgs, _, err := c.Organizations.List(
ctx,
*(user.Name),
nil,
)

if len(orgs) != 1 {
t.Errorf("Orgs len is %d want 1", len(orgs))
}

if err != nil {
t.Errorf("Err is %s, want nil", err.Error())
}

if *(orgs[0].Name) != "foobar123thisorgwasmocked" {
t.Errorf("orgs[0].Name is %s, want %s", *orgs[0].Name, "foobar123thisorgdoesnotexist")
}
}
174 changes: 174 additions & 0 deletions mock/gen/gen-request-matches.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading