Skip to content

Commit 3000847

Browse files
authored
Merge pull request cli#874 from cli/httpmock
Parallelism-safe mechanism for stubbing HTTP responses
2 parents 2621b85 + 0a4d4ee commit 3000847

File tree

11 files changed

+288
-160
lines changed

11 files changed

+288
-160
lines changed

api/client_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"io/ioutil"
66
"reflect"
77
"testing"
8+
9+
"github.com/cli/cli/pkg/httpmock"
810
)
911

1012
func eq(t *testing.T, got interface{}, expected interface{}) {
@@ -15,7 +17,7 @@ func eq(t *testing.T, got interface{}, expected interface{}) {
1517
}
1618

1719
func TestGraphQL(t *testing.T) {
18-
http := &FakeHTTP{}
20+
http := &httpmock.Registry{}
1921
client := NewClient(
2022
ReplaceTripper(http),
2123
AddHeader("Authorization", "token OTOKEN"),
@@ -40,7 +42,7 @@ func TestGraphQL(t *testing.T) {
4042
}
4143

4244
func TestGraphQLError(t *testing.T) {
43-
http := &FakeHTTP{}
45+
http := &httpmock.Registry{}
4446
client := NewClient(ReplaceTripper(http))
4547

4648
response := struct{}{}
@@ -52,7 +54,7 @@ func TestGraphQLError(t *testing.T) {
5254
}
5355

5456
func TestRESTGetDelete(t *testing.T) {
55-
http := &FakeHTTP{}
57+
http := &httpmock.Registry{}
5658

5759
client := NewClient(
5860
ReplaceTripper(http),

api/fake_http.go

Lines changed: 0 additions & 120 deletions
This file was deleted.

api/queries_issue_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import (
77
"testing"
88

99
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/httpmock"
1011
)
1112

1213
func TestIssueList(t *testing.T) {
13-
http := &FakeHTTP{}
14+
http := &httpmock.Registry{}
1415
client := NewClient(ReplaceTripper(http))
1516

1617
http.StubResponse(200, bytes.NewBufferString(`

api/queries_repo_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"encoding/json"
66
"io/ioutil"
77
"testing"
8+
9+
"github.com/cli/cli/pkg/httpmock"
810
)
911

1012
func Test_RepoCreate(t *testing.T) {
11-
http := &FakeHTTP{}
13+
http := &httpmock.Registry{}
1214
client := NewClient(ReplaceTripper(http))
1315

1416
http.StubResponse(200, bytes.NewBufferString(`{}`))

command/pr_create_test.go

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/cli/cli/context"
1111
"github.com/cli/cli/git"
12+
"github.com/cli/cli/pkg/httpmock"
1213
"github.com/cli/cli/test"
1314
)
1415

@@ -408,20 +409,27 @@ func TestPRCreate_survey_defaults_multicommit(t *testing.T) {
408409
func TestPRCreate_survey_defaults_monocommit(t *testing.T) {
409410
initBlankContext("", "OWNER/REPO", "feature")
410411
http := initFakeHTTP()
411-
http.StubRepoResponse("OWNER", "REPO")
412-
http.StubResponse(200, bytes.NewBufferString(`
413-
{ "data": { "repository": { "forks": { "nodes": [
414-
] } } } }
412+
defer http.Verify(t)
413+
http.Register(httpmock.GraphQL(`\bviewerPermission\b`), httpmock.StringResponse(httpmock.RepoNetworkStubResponse("OWNER", "REPO", "master", "WRITE")))
414+
http.Register(httpmock.GraphQL(`\bforks\(`), httpmock.StringResponse(`
415+
{ "data": { "repository": { "forks": { "nodes": [
416+
] } } } }
415417
`))
416-
http.StubResponse(200, bytes.NewBufferString(`
418+
http.Register(httpmock.GraphQL(`\bpullRequests\(`), httpmock.StringResponse(`
417419
{ "data": { "repository": { "pullRequests": { "nodes" : [
418420
] } } } }
419421
`))
420-
http.StubResponse(200, bytes.NewBufferString(`
422+
http.Register(httpmock.GraphQL(`\bcreatePullRequest\(`), httpmock.GraphQLMutation(`
421423
{ "data": { "createPullRequest": { "pullRequest": {
422424
"URL": "https://github.com/OWNER/REPO/pull/12"
423425
} } } }
424-
`))
426+
`, func(inputs map[string]interface{}) {
427+
eq(t, inputs["repositoryId"], "REPOID")
428+
eq(t, inputs["title"], "the sky above the port")
429+
eq(t, inputs["body"], "was the color of a television, turned to a dead channel")
430+
eq(t, inputs["baseRefName"], "master")
431+
eq(t, inputs["headRefName"], "feature")
432+
}))
425433

426434
cs, cmdTeardown := test.InitCmdStubber()
427435
defer cmdTeardown()
@@ -456,29 +464,6 @@ func TestPRCreate_survey_defaults_monocommit(t *testing.T) {
456464

457465
output, err := RunCommand(`pr create`)
458466
eq(t, err, nil)
459-
460-
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
461-
reqBody := struct {
462-
Variables struct {
463-
Input struct {
464-
RepositoryID string
465-
Title string
466-
Body string
467-
BaseRefName string
468-
HeadRefName string
469-
}
470-
}
471-
}{}
472-
_ = json.Unmarshal(bodyBytes, &reqBody)
473-
474-
expectedBody := "was the color of a television, turned to a dead channel"
475-
476-
eq(t, reqBody.Variables.Input.RepositoryID, "REPOID")
477-
eq(t, reqBody.Variables.Input.Title, "the sky above the port")
478-
eq(t, reqBody.Variables.Input.Body, expectedBody)
479-
eq(t, reqBody.Variables.Input.BaseRefName, "master")
480-
eq(t, reqBody.Variables.Input.HeadRefName, "feature")
481-
482467
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
483468
}
484469

command/testing.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/cli/cli/api"
1212
"github.com/cli/cli/context"
1313
"github.com/cli/cli/internal/config"
14+
"github.com/cli/cli/pkg/httpmock"
1415
"github.com/google/shlex"
1516
"github.com/spf13/pflag"
1617
)
@@ -93,8 +94,8 @@ func initBlankContext(cfg, repo, branch string) {
9394
}
9495
}
9596

96-
func initFakeHTTP() *api.FakeHTTP {
97-
http := &api.FakeHTTP{}
97+
func initFakeHTTP() *httpmock.Registry {
98+
http := &httpmock.Registry{}
9899
apiClientForContext = func(context.Context) (*api.Client, error) {
99100
return api.NewClient(api.ReplaceTripper(http)), nil
100101
}

context/remote_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/cli/cli/api"
1111
"github.com/cli/cli/git"
1212
"github.com/cli/cli/internal/ghrepo"
13+
"github.com/cli/cli/pkg/httpmock"
1314
)
1415

1516
func eq(t *testing.T, got interface{}, expected interface{}) {
@@ -70,7 +71,7 @@ func Test_translateRemotes(t *testing.T) {
7071
}
7172

7273
func Test_resolvedRemotes_triangularSetup(t *testing.T) {
73-
http := &api.FakeHTTP{}
74+
http := &httpmock.Registry{}
7475
apiClient := api.NewClient(api.ReplaceTripper(http))
7576

7677
http.StubResponse(200, bytes.NewBufferString(`
@@ -137,7 +138,7 @@ func Test_resolvedRemotes_triangularSetup(t *testing.T) {
137138
}
138139

139140
func Test_resolvedRemotes_forkLookup(t *testing.T) {
140-
http := &api.FakeHTTP{}
141+
http := &httpmock.Registry{}
141142
apiClient := api.NewClient(api.ReplaceTripper(http))
142143

143144
http.StubResponse(200, bytes.NewBufferString(`

pkg/httpmock/legacy.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package httpmock
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"os"
8+
"path"
9+
"strings"
10+
)
11+
12+
// TODO: clean up methods in this file when there are no more callers
13+
14+
func (r *Registry) StubResponse(status int, body io.Reader) {
15+
r.Register(MatchAny, func(*http.Request) (*http.Response, error) {
16+
return httpResponse(status, body), nil
17+
})
18+
}
19+
20+
func (r *Registry) StubWithFixture(status int, fixtureFileName string) func() {
21+
fixturePath := path.Join("../test/fixtures/", fixtureFileName)
22+
fixtureFile, err := os.Open(fixturePath)
23+
r.Register(MatchAny, func(*http.Request) (*http.Response, error) {
24+
if err != nil {
25+
return nil, err
26+
}
27+
return httpResponse(200, fixtureFile), nil
28+
})
29+
return func() {
30+
if err == nil {
31+
fixtureFile.Close()
32+
}
33+
}
34+
}
35+
36+
func (r *Registry) StubRepoResponse(owner, repo string) {
37+
r.StubRepoResponseWithPermission(owner, repo, "WRITE")
38+
}
39+
40+
func (r *Registry) StubRepoResponseWithPermission(owner, repo, permission string) {
41+
r.Register(MatchAny, StringResponse(RepoNetworkStubResponse(owner, repo, "master", permission)))
42+
}
43+
44+
func (r *Registry) StubRepoResponseWithDefaultBranch(owner, repo, defaultBranch string) {
45+
r.Register(MatchAny, StringResponse(RepoNetworkStubResponse(owner, repo, defaultBranch, "WRITE")))
46+
}
47+
48+
func (r *Registry) StubForkedRepoResponse(ownRepo, parentRepo string) {
49+
r.Register(MatchAny, StringResponse(RepoNetworkStubForkResponse(ownRepo, parentRepo)))
50+
}
51+
52+
func RepoNetworkStubResponse(owner, repo, defaultBranch, permission string) string {
53+
return fmt.Sprintf(`
54+
{ "data": { "repo_000": {
55+
"id": "REPOID",
56+
"name": "%s",
57+
"owner": {"login": "%s"},
58+
"defaultBranchRef": {
59+
"name": "%s"
60+
},
61+
"viewerPermission": "%s"
62+
} } }
63+
`, repo, owner, defaultBranch, permission)
64+
}
65+
66+
func RepoNetworkStubForkResponse(forkFullName, parentFullName string) string {
67+
forkRepo := strings.SplitN(forkFullName, "/", 2)
68+
parentRepo := strings.SplitN(parentFullName, "/", 2)
69+
return fmt.Sprintf(`
70+
{ "data": { "repo_000": {
71+
"id": "REPOID2",
72+
"name": "%s",
73+
"owner": {"login": "%s"},
74+
"defaultBranchRef": {
75+
"name": "master"
76+
},
77+
"viewerPermission": "ADMIN",
78+
"parent": {
79+
"id": "REPOID1",
80+
"name": "%s",
81+
"owner": {"login": "%s"},
82+
"defaultBranchRef": {
83+
"name": "master"
84+
},
85+
"viewerPermission": "READ"
86+
}
87+
} } }
88+
`, forkRepo[1], forkRepo[0], parentRepo[1], parentRepo[0])
89+
}

0 commit comments

Comments
 (0)