1package diff 2 3import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "strings" 9 "testing" 10 11 "github.com/cli/cli/v2/api" 12 "github.com/cli/cli/v2/internal/ghrepo" 13 "github.com/cli/cli/v2/pkg/cmd/pr/shared" 14 "github.com/cli/cli/v2/pkg/cmdutil" 15 "github.com/cli/cli/v2/pkg/httpmock" 16 "github.com/cli/cli/v2/pkg/iostreams" 17 "github.com/google/go-cmp/cmp" 18 "github.com/google/shlex" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21) 22 23func Test_NewCmdDiff(t *testing.T) { 24 tests := []struct { 25 name string 26 args string 27 isTTY bool 28 want DiffOptions 29 wantErr string 30 }{ 31 { 32 name: "number argument", 33 args: "123", 34 isTTY: true, 35 want: DiffOptions{ 36 SelectorArg: "123", 37 UseColor: true, 38 }, 39 }, 40 { 41 name: "no argument", 42 args: "", 43 isTTY: true, 44 want: DiffOptions{ 45 SelectorArg: "", 46 UseColor: true, 47 }, 48 }, 49 { 50 name: "no color when redirected", 51 args: "", 52 isTTY: false, 53 want: DiffOptions{ 54 SelectorArg: "", 55 UseColor: false, 56 }, 57 }, 58 { 59 name: "force color", 60 args: "--color always", 61 isTTY: false, 62 want: DiffOptions{ 63 SelectorArg: "", 64 UseColor: true, 65 }, 66 }, 67 { 68 name: "disable color", 69 args: "--color never", 70 isTTY: true, 71 want: DiffOptions{ 72 SelectorArg: "", 73 UseColor: false, 74 }, 75 }, 76 { 77 name: "no argument with --repo override", 78 args: "-R owner/repo", 79 isTTY: true, 80 wantErr: "argument required when using the `--repo` flag", 81 }, 82 { 83 name: "invalid --color argument", 84 args: "--color doublerainbow", 85 isTTY: true, 86 wantErr: "the value for `--color` must be one of \"auto\", \"always\", or \"never\"", 87 }, 88 } 89 for _, tt := range tests { 90 t.Run(tt.name, func(t *testing.T) { 91 io, _, _, _ := iostreams.Test() 92 io.SetStdoutTTY(tt.isTTY) 93 io.SetStdinTTY(tt.isTTY) 94 io.SetStderrTTY(tt.isTTY) 95 io.SetColorEnabled(tt.isTTY) 96 97 f := &cmdutil.Factory{ 98 IOStreams: io, 99 } 100 101 var opts *DiffOptions 102 cmd := NewCmdDiff(f, func(o *DiffOptions) error { 103 opts = o 104 return nil 105 }) 106 cmd.PersistentFlags().StringP("repo", "R", "", "") 107 108 argv, err := shlex.Split(tt.args) 109 require.NoError(t, err) 110 cmd.SetArgs(argv) 111 112 cmd.SetIn(&bytes.Buffer{}) 113 cmd.SetOut(ioutil.Discard) 114 cmd.SetErr(ioutil.Discard) 115 116 _, err = cmd.ExecuteC() 117 if tt.wantErr != "" { 118 require.EqualError(t, err, tt.wantErr) 119 return 120 } else { 121 require.NoError(t, err) 122 } 123 124 assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) 125 assert.Equal(t, tt.want.UseColor, opts.UseColor) 126 }) 127 } 128} 129 130func Test_diffRun(t *testing.T) { 131 pr := &api.PullRequest{Number: 123} 132 133 tests := []struct { 134 name string 135 opts DiffOptions 136 rawDiff string 137 wantAccept string 138 wantStdout string 139 }{ 140 { 141 name: "no color", 142 opts: DiffOptions{ 143 SelectorArg: "123", 144 UseColor: false, 145 Patch: false, 146 }, 147 rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), 148 wantAccept: "application/vnd.github.v3.diff", 149 wantStdout: fmt.Sprintf(testDiff, "", "", "", ""), 150 }, 151 { 152 name: "with color", 153 opts: DiffOptions{ 154 SelectorArg: "123", 155 UseColor: true, 156 Patch: false, 157 }, 158 rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), 159 wantAccept: "application/vnd.github.v3.diff", 160 wantStdout: fmt.Sprintf(testDiff, "\x1b[m", "\x1b[1;38m", "\x1b[32m", "\x1b[31m"), 161 }, 162 { 163 name: "patch format", 164 opts: DiffOptions{ 165 SelectorArg: "123", 166 UseColor: false, 167 Patch: true, 168 }, 169 rawDiff: fmt.Sprintf(testDiff, "", "", "", ""), 170 wantAccept: "application/vnd.github.v3.patch", 171 wantStdout: fmt.Sprintf(testDiff, "", "", "", ""), 172 }, 173 } 174 for _, tt := range tests { 175 t.Run(tt.name, func(t *testing.T) { 176 httpReg := &httpmock.Registry{} 177 defer httpReg.Verify(t) 178 179 var gotAccept string 180 httpReg.Register( 181 httpmock.REST("GET", "repos/OWNER/REPO/pulls/123"), 182 func(req *http.Request) (*http.Response, error) { 183 gotAccept = req.Header.Get("Accept") 184 return &http.Response{ 185 StatusCode: 200, 186 Request: req, 187 Body: ioutil.NopCloser(strings.NewReader(tt.rawDiff)), 188 }, nil 189 }) 190 191 opts := tt.opts 192 opts.HttpClient = func() (*http.Client, error) { 193 return &http.Client{Transport: httpReg}, nil 194 } 195 196 io, _, stdout, stderr := iostreams.Test() 197 opts.IO = io 198 199 finder := shared.NewMockFinder("123", pr, ghrepo.New("OWNER", "REPO")) 200 finder.ExpectFields([]string{"number"}) 201 opts.Finder = finder 202 203 if err := diffRun(&opts); err != nil { 204 t.Fatalf("unexpected error: %s", err) 205 } 206 if diff := cmp.Diff(tt.wantStdout, stdout.String()); diff != "" { 207 t.Errorf("command output did not match:\n%s", diff) 208 } 209 if stderr.String() != "" { 210 t.Errorf("unexpected stderr output: %s", stderr.String()) 211 } 212 if gotAccept != tt.wantAccept { 213 t.Errorf("unexpected Accept header: %s", gotAccept) 214 } 215 }) 216 } 217} 218 219const testDiff = `%[2]sdiff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml%[1]s 220%[2]sindex 73974448..b7fc0154 100644%[1]s 221%[2]s--- a/.github/workflows/releases.yml%[1]s 222%[2]s+++ b/.github/workflows/releases.yml%[1]s 223@@ -44,6 +44,11 @@ jobs: 224 token: ${{secrets.SITE_GITHUB_TOKEN}} 225 - name: Publish documentation site 226 if: "!contains(github.ref, '-')" # skip prereleases 227%[3]s+ env:%[1]s 228%[3]s+ GIT_COMMITTER_NAME: cli automation%[1]s 229%[3]s+ GIT_AUTHOR_NAME: cli automation%[1]s 230%[3]s+ GIT_COMMITTER_EMAIL: noreply@github.com%[1]s 231%[3]s+ GIT_AUTHOR_EMAIL: noreply@github.com%[1]s 232 run: make site-publish 233 - name: Move project cards 234 if: "!contains(github.ref, '-')" # skip prereleases 235%[2]sdiff --git a/Makefile b/Makefile%[1]s 236%[2]sindex f2b4805c..3d7bd0f9 100644%[1]s 237%[2]s--- a/Makefile%[1]s 238%[2]s+++ b/Makefile%[1]s 239@@ -22,8 +22,8 @@ test: 240 go test ./... 241 .PHONY: test 242 243%[4]s-site:%[1]s 244%[4]s- git clone https://github.com/github/cli.github.com.git "$@"%[1]s 245%[3]s+site: bin/gh%[1]s 246%[3]s+ bin/gh repo clone github/cli.github.com "$@"%[1]s 247 248 site-docs: site 249 git -C site pull 250` 251 252func Test_colorDiffLines(t *testing.T) { 253 inputs := []struct { 254 input, output string 255 }{ 256 { 257 input: "", 258 output: "", 259 }, 260 { 261 input: "\n", 262 output: "\n", 263 }, 264 { 265 input: "foo\nbar\nbaz\n", 266 output: "foo\nbar\nbaz\n", 267 }, 268 { 269 input: "foo\nbar\nbaz", 270 output: "foo\nbar\nbaz\n", 271 }, 272 { 273 input: fmt.Sprintf("+foo\n-b%sr\n+++ baz\n", strings.Repeat("a", 2*lineBufferSize)), 274 output: fmt.Sprintf( 275 "%[4]s+foo%[2]s\n%[5]s-b%[1]sr%[2]s\n%[3]s+++ baz%[2]s\n", 276 strings.Repeat("a", 2*lineBufferSize), 277 "\x1b[m", 278 "\x1b[1;38m", 279 "\x1b[32m", 280 "\x1b[31m", 281 ), 282 }, 283 } 284 for _, tt := range inputs { 285 buf := bytes.Buffer{} 286 if err := colorDiffLines(&buf, strings.NewReader(tt.input)); err != nil { 287 t.Fatalf("unexpected error: %s", err) 288 } 289 if got := buf.String(); got != tt.output { 290 t.Errorf("expected: %q, got: %q", tt.output, got) 291 } 292 } 293} 294