diff --git a/api/client.go b/api/client.go index 0e45c31f1..1dcf7c0ce 100644 --- a/api/client.go +++ b/api/client.go @@ -21,7 +21,7 @@ type graphQLResponse struct { } /* -graphQL usage +GraphQL: Declared as an external variable so it can be mocked in tests type repoResponse struct { Repository struct { @@ -45,7 +45,7 @@ if err != nil { fmt.Printf("%+v\n", resp) */ -func graphQL(query string, variables map[string]string, data interface{}) error { +var GraphQL = func(query string, variables map[string]string, data interface{}) error { url := "https://api.github.com/graphql" reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables}) if err != nil { diff --git a/api/queries.go b/api/queries.go index a4c023375..cc7a5e512 100644 --- a/api/queries.go +++ b/api/queries.go @@ -98,7 +98,7 @@ func PullRequests() (PullRequestsPayload, error) { } var resp response - err := graphQL(query, variables, &resp) + err := GraphQL(query, variables, &resp) if err != nil { return PullRequestsPayload{}, err } @@ -133,7 +133,6 @@ func project() github.Project { if error != nil { panic(error) } - for _, remote := range remotes { if project, error := remote.Project(); error == nil { return *project diff --git a/command/pr_test.go b/command/pr_test.go new file mode 100644 index 000000000..ced88b3b8 --- /dev/null +++ b/command/pr_test.go @@ -0,0 +1,34 @@ +package command + +import ( + "regexp" + "testing" + + "github.com/github/gh-cli/test" +) + +func TestPRList(t *testing.T) { + teardown := test.MockGraphQLResponse("test/fixtures/pr.json") + defer teardown() + + gitRepo := test.UseTempGitRepo() + defer gitRepo.TearDown() + + output, err := test.RunCommand(RootCmd, "pr list") + if err != nil { + t.Errorf("error running command `pr list`: %v", err) + } + + expectedPrs := []*regexp.Regexp{ + regexp.MustCompile(`#8.*\[strawberries\]`), + regexp.MustCompile(`#9.*\[apples\]`), + regexp.MustCompile(`#10.*\[blueberries\]`), + regexp.MustCompile(`#11.*\[figs\]`), + } + + for _, r := range expectedPrs { + if !r.MatchString(output) { + t.Errorf("output did not match regexp /%s/", r) + } + } +} diff --git a/test/fixtures/pr.json b/test/fixtures/pr.json new file mode 100644 index 000000000..d3a7e89f2 --- /dev/null +++ b/test/fixtures/pr.json @@ -0,0 +1,50 @@ +{ + "repository": { + "pullRequests": { + "edges": [ + { + "node": { + "number": 10, + "title": "Blueberries are a good fruit", + "url": "https://github.com/github/gh-cli/pull/10", + "headRefName": "[blueberries]" + } + } + ] + } + }, + "viewerCreated": { + "edges": [ + { + "node": { + "number": 8, + "title": "Strawberries are not actually berries", + "url": "https://github.com/github/gh-cli/pull/8", + "headRefName": "[strawberries]" + } + } + ], + "pageInfo": { "hasNextPage": false } + }, + "reviewRequested": { + "edges": [ + { + "node": { + "number": 9, + "title": "Apples are tasty", + "url": "https://github.com/github/gh-cli/pull/9", + "headRefName": "[apples]" + } + }, + { + "node": { + "number": 11, + "title": "Figs are my favorite", + "url": "https://github.com/github/gh-cli/pull/1", + "headRefName": "[figs]" + } + } + ], + "pageInfo": { "hasNextPage": false } + } +} diff --git a/test/fixtures/test.git/HEAD b/test/fixtures/test.git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/fixtures/test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixtures/test.git/config b/test/fixtures/test.git/config new file mode 100644 index 000000000..4f7d452b3 --- /dev/null +++ b/test/fixtures/test.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = false diff --git a/test/fixtures/test.git/info/exclude b/test/fixtures/test.git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/test/fixtures/test.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/fixtures/test.git/objects/08/f4b7b6513dffc6245857e497cfd6101dc47818 b/test/fixtures/test.git/objects/08/f4b7b6513dffc6245857e497cfd6101dc47818 new file mode 100644 index 000000000..37162913a --- /dev/null +++ b/test/fixtures/test.git/objects/08/f4b7b6513dffc6245857e497cfd6101dc47818 @@ -0,0 +1,3 @@ +xM +0F]seb~nxcMi)^ߠ7p>xb9U7ub{bgٴă#h8>4o +'yȜ],ڄFk-jUW*zI); \ No newline at end of file diff --git a/test/fixtures/test.git/objects/8a/1cdac440b4a3c44b988e300758a903a9866905 b/test/fixtures/test.git/objects/8a/1cdac440b4a3c44b988e300758a903a9866905 new file mode 100644 index 000000000..27427c268 Binary files /dev/null and b/test/fixtures/test.git/objects/8a/1cdac440b4a3c44b988e300758a903a9866905 differ diff --git a/test/fixtures/test.git/objects/9b/5a719a3d76ac9dc2fa635d9b1f34fd73994c06 b/test/fixtures/test.git/objects/9b/5a719a3d76ac9dc2fa635d9b1f34fd73994c06 new file mode 100644 index 000000000..7a03f1760 --- /dev/null +++ b/test/fixtures/test.git/objects/9b/5a719a3d76ac9dc2fa635d9b1f34fd73994c06 @@ -0,0 +1,3 @@ +xK +1D]}3$qBϐdq&#^s7UPūXqh/ZM w*cbcG3YIk g +6-Us4i61F#Y,K0iG|=~b7 $b KdD13eKnZC՟{Nm \ No newline at end of file diff --git a/test/fixtures/test.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 b/test/fixtures/test.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 new file mode 100644 index 000000000..4667dcf6f Binary files /dev/null and b/test/fixtures/test.git/objects/9d/aeafb9864cf43055ae93beb0afd6c7d144bfa4 differ diff --git a/test/fixtures/test.git/objects/ca/93b49848670d03b3968c8a481eca55f5fb2150 b/test/fixtures/test.git/objects/ca/93b49848670d03b3968c8a481eca55f5fb2150 new file mode 100644 index 000000000..4d0f210ec Binary files /dev/null and b/test/fixtures/test.git/objects/ca/93b49848670d03b3968c8a481eca55f5fb2150 differ diff --git a/test/fixtures/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/test/fixtures/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 000000000..711223894 Binary files /dev/null and b/test/fixtures/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/test/fixtures/test.git/refs/heads/master b/test/fixtures/test.git/refs/heads/master new file mode 100644 index 000000000..fc54b05cb --- /dev/null +++ b/test/fixtures/test.git/refs/heads/master @@ -0,0 +1 @@ +9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06 diff --git a/test/helpers.go b/test/helpers.go new file mode 100644 index 000000000..ce2de88df --- /dev/null +++ b/test/helpers.go @@ -0,0 +1,130 @@ +package test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/github/gh-cli/api" + "github.com/github/gh-cli/github" + "github.com/spf13/cobra" +) + +type TempGitRepo struct { + Remote string + TearDown func() +} + +func UseTempGitRepo() *TempGitRepo { + github.CreateTestConfigs("mario", "i-love-peach") + + pwd, _ := os.Getwd() + oldEnv := make(map[string]string) + overrideEnv := func(name, value string) { + oldEnv[name] = os.Getenv(name) + os.Setenv(name, value) + } + + remotePath := filepath.Join(pwd, "..", "test", "fixtures", "test.git") + home, err := ioutil.TempDir("", "test-repo") + if err != nil { + panic(err) + } + + overrideEnv("HOME", home) + overrideEnv("XDG_CONFIG_HOME", "") + overrideEnv("XDG_CONFIG_DIRS", "") + + targetPath := filepath.Join(home, "test.git") + cmd := exec.Command("git", "clone", remotePath, targetPath) + if output, err := cmd.Output(); err != nil { + panic(fmt.Errorf("error running %s\n%s\n%s", cmd, err, output)) + } + + if err = os.Chdir(targetPath); err != nil { + panic(err) + } + + // Our libs expect the origin to be a github url + cmd = exec.Command("git", "remote", "set-url", "origin", "https://github.com/github/FAKE-GITHUB-REPO-NAME") + if output, err := cmd.Output(); err != nil { + panic(fmt.Errorf("error running %s\n%s\n%s", cmd, err, output)) + } + + tearDown := func() { + if err := os.Chdir(pwd); err != nil { + panic(err) + } + for name, value := range oldEnv { + os.Setenv(name, value) + } + if err = os.RemoveAll(home); err != nil { + panic(err) + } + } + + return &TempGitRepo{Remote: remotePath, TearDown: tearDown} +} + +func MockGraphQLResponse(fixturePath string) (teardown func()) { + pwd, _ := os.Getwd() + fixturePath = filepath.Join(pwd, "..", fixturePath) + + originalGraphQL := api.GraphQL + api.GraphQL = func(query string, variables map[string]string, v interface{}) error { + contents, err := ioutil.ReadFile(fixturePath) + if err != nil { + return err + } + + json.Unmarshal(contents, &v) + if err != nil { + return err + } + + return nil + } + + return func() { + api.GraphQL = originalGraphQL + } +} + +func RunCommand(root *cobra.Command, s string) (string, error) { + var err error + output := captureOutput(func() { + root.SetArgs(strings.Split(s, " ")) + _, err = root.ExecuteC() + }) + if err != nil { + return "", err + } + return output, nil +} + +func captureOutput(f func()) string { + originalStdout := os.Stdout + defer func() { + os.Stdout = originalStdout + }() + + r, w, err := os.Pipe() + if err != nil { + panic("failed to pipe stdout") + } + os.Stdout = w + + f() + + w.Close() + out, err := ioutil.ReadAll(r) + if err != nil { + panic("failed to read captured input from stdout") + } + + return string(out) +}