package diff import ( "bytes" "fmt" "io" "net/http" "strings" "testing" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/go-cmp/cmp" "github.com/google/shlex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_NewCmdDiff(t *testing.T) { tests := []struct { name string args string isTTY bool want DiffOptions wantErr string }{ { name: "number argument", args: "123", isTTY: true, want: DiffOptions{ SelectorArg: "123", UseColor: true, }, }, { name: "no argument", args: "", isTTY: true, want: DiffOptions{ SelectorArg: "", UseColor: true, }, }, { name: "no color when redirected", args: "", isTTY: false, want: DiffOptions{ SelectorArg: "", UseColor: false, }, }, { name: "force color", args: "--color always", isTTY: false, want: DiffOptions{ SelectorArg: "", UseColor: true, }, }, { name: "disable color", args: "--color never", isTTY: true, want: DiffOptions{ SelectorArg: "", UseColor: false, }, }, { name: "no argument with --repo override", args: "-R owner/repo", isTTY: true, wantErr: "argument required when using the `--repo` flag", }, { name: "invalid --color argument", args: "--color doublerainbow", isTTY: true, wantErr: "invalid argument \"doublerainbow\" for \"--color\" flag: valid values are {always|never|auto}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() ios.SetStdoutTTY(tt.isTTY) ios.SetStdinTTY(tt.isTTY) ios.SetStderrTTY(tt.isTTY) ios.SetColorEnabled(tt.isTTY) f := &cmdutil.Factory{ IOStreams: ios, } var opts *DiffOptions cmd := NewCmdDiff(f, func(o *DiffOptions) error { opts = o return nil }) cmd.PersistentFlags().StringP("repo", "R", "", "") argv, err := shlex.Split(tt.args) require.NoError(t, err) cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) _, err = cmd.ExecuteC() if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) return } else { require.NoError(t, err) } assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) assert.Equal(t, tt.want.UseColor, opts.UseColor) }) } } func Test_diffRun(t *testing.T) { pr := &api.PullRequest{Number: 123} tests := []struct { name string opts DiffOptions rawDiff string wantAccept string wantStdout string }{ { name: "no color", opts: DiffOptions{ SelectorArg: "123", UseColor: false, Patch: false, }, rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), wantAccept: "application/vnd.github.v3.diff", wantStdout: fmt.Sprintf(testDiff, "", "", "", ""), }, { name: "with color", opts: DiffOptions{ SelectorArg: "123", UseColor: true, Patch: false, }, rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), wantAccept: "application/vnd.github.v3.diff", wantStdout: fmt.Sprintf(testDiff, "\x1b[m", "\x1b[1;38m", "\x1b[32m", "\x1b[31m"), }, { name: "patch format", opts: DiffOptions{ SelectorArg: "123", UseColor: false, Patch: true, }, rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), wantAccept: "application/vnd.github.v3.patch", wantStdout: fmt.Sprintf(testDiff, "", "", "", ""), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { httpReg := &httpmock.Registry{} defer httpReg.Verify(t) var gotAccept string httpReg.Register( httpmock.REST("GET", "repos/OWNER/REPO/pulls/123"), func(req *http.Request) (*http.Response, error) { gotAccept = req.Header.Get("Accept") return &http.Response{ StatusCode: 200, Request: req, Body: io.NopCloser(strings.NewReader(tt.rawDiff)), }, nil }) opts := tt.opts opts.HttpClient = func() (*http.Client, error) { return &http.Client{Transport: httpReg}, nil } ios, _, stdout, stderr := iostreams.Test() opts.IO = ios finder := shared.NewMockFinder("123", pr, ghrepo.New("OWNER", "REPO")) finder.ExpectFields([]string{"number"}) opts.Finder = finder if err := diffRun(&opts); err != nil { t.Fatalf("unexpected error: %s", err) } if diff := cmp.Diff(tt.wantStdout, stdout.String()); diff != "" { t.Errorf("command output did not match:\n%s", diff) } if stderr.String() != "" { t.Errorf("unexpected stderr output: %s", stderr.String()) } if gotAccept != tt.wantAccept { t.Errorf("unexpected Accept header: %s", gotAccept) } }) } } const testDiff = `%[2]sdiff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml%[1]s %[2]sindex 73974448..b7fc0154 100644%[1]s %[2]s--- a/.github/workflows/releases.yml%[1]s %[2]s+++ b/.github/workflows/releases.yml%[1]s @@ -44,6 +44,11 @@ jobs: token: ${{secrets.SITE_GITHUB_TOKEN}} - name: Publish documentation site if: "!contains(github.ref, '-')" # skip prereleases %[3]s+ env:%[1]s %[3]s+ GIT_COMMITTER_NAME: cli automation%[1]s %[3]s+ GIT_AUTHOR_NAME: cli automation%[1]s %[3]s+ GIT_COMMITTER_EMAIL: noreply@github.com%[1]s %[3]s+ GIT_AUTHOR_EMAIL: noreply@github.com%[1]s run: make site-publish - name: Move project cards if: "!contains(github.ref, '-')" # skip prereleases %[2]sdiff --git a/Makefile b/Makefile%[1]s %[2]sindex f2b4805c..3d7bd0f9 100644%[1]s %[2]s--- a/Makefile%[1]s %[2]s+++ b/Makefile%[1]s @@ -22,8 +22,8 @@ test: go test ./... .PHONY: test %[4]s-site:%[1]s %[4]s- git clone https://github.com/github/cli.github.com.git "$@"%[1]s %[3]s+site: bin/gh%[1]s %[3]s+ bin/gh repo clone github/cli.github.com "$@"%[1]s site-docs: site git -C site pull ` func Test_colorDiffLines(t *testing.T) { inputs := []struct { input, output string }{ { input: "", output: "", }, { input: "\n", output: "\n", }, { input: "foo\nbar\nbaz\n", output: "foo\nbar\nbaz\n", }, { input: "foo\nbar\nbaz", output: "foo\nbar\nbaz\n", }, { input: fmt.Sprintf("+foo\n-b%sr\n+++ baz\n", strings.Repeat("a", 2*lineBufferSize)), output: fmt.Sprintf( "%[4]s+foo%[2]s\n%[5]s-b%[1]sr%[2]s\n%[3]s+++ baz%[2]s\n", strings.Repeat("a", 2*lineBufferSize), "\x1b[m", "\x1b[1;38m", "\x1b[32m", "\x1b[31m", ), }, } for _, tt := range inputs { buf := bytes.Buffer{} if err := colorDiffLines(&buf, strings.NewReader(tt.input)); err != nil { t.Fatalf("unexpected error: %s", err) } if got := buf.String(); got != tt.output { t.Errorf("expected: %q, got: %q", tt.output, got) } } }