github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/diff/diff_test.go (about)

     1  package diff
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/ungtb10d/cli/v2/api"
    12  	"github.com/ungtb10d/cli/v2/internal/browser"
    13  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    14  	"github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared"
    15  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    16  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    17  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    18  	"github.com/google/shlex"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func 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: "name only",
    33  			args: "--name-only",
    34  			want: DiffOptions{
    35  				NameOnly: true,
    36  			},
    37  		},
    38  		{
    39  			name:  "number argument",
    40  			args:  "123",
    41  			isTTY: true,
    42  			want: DiffOptions{
    43  				SelectorArg: "123",
    44  				UseColor:    true,
    45  			},
    46  		},
    47  		{
    48  			name:  "no argument",
    49  			args:  "",
    50  			isTTY: true,
    51  			want: DiffOptions{
    52  				SelectorArg: "",
    53  				UseColor:    true,
    54  			},
    55  		},
    56  		{
    57  			name:  "no color when redirected",
    58  			args:  "",
    59  			isTTY: false,
    60  			want: DiffOptions{
    61  				SelectorArg: "",
    62  				UseColor:    false,
    63  			},
    64  		},
    65  		{
    66  			name:  "force color",
    67  			args:  "--color always",
    68  			isTTY: false,
    69  			want: DiffOptions{
    70  				SelectorArg: "",
    71  				UseColor:    true,
    72  			},
    73  		},
    74  		{
    75  			name:  "disable color",
    76  			args:  "--color never",
    77  			isTTY: true,
    78  			want: DiffOptions{
    79  				SelectorArg: "",
    80  				UseColor:    false,
    81  			},
    82  		},
    83  		{
    84  			name:    "no argument with --repo override",
    85  			args:    "-R owner/repo",
    86  			isTTY:   true,
    87  			wantErr: "argument required when using the `--repo` flag",
    88  		},
    89  		{
    90  			name:    "invalid --color argument",
    91  			args:    "--color doublerainbow",
    92  			isTTY:   true,
    93  			wantErr: "invalid argument \"doublerainbow\" for \"--color\" flag: valid values are {always|never|auto}",
    94  		},
    95  		{
    96  			name:  "web mode",
    97  			args:  "123 --web",
    98  			isTTY: true,
    99  			want: DiffOptions{
   100  				SelectorArg: "123",
   101  				UseColor:    true,
   102  				BrowserMode: true,
   103  			},
   104  		},
   105  	}
   106  	for _, tt := range tests {
   107  		t.Run(tt.name, func(t *testing.T) {
   108  			ios, _, _, _ := iostreams.Test()
   109  			ios.SetStdoutTTY(tt.isTTY)
   110  			ios.SetStdinTTY(tt.isTTY)
   111  			ios.SetStderrTTY(tt.isTTY)
   112  			ios.SetColorEnabled(tt.isTTY)
   113  
   114  			f := &cmdutil.Factory{
   115  				IOStreams: ios,
   116  			}
   117  
   118  			var opts *DiffOptions
   119  			cmd := NewCmdDiff(f, func(o *DiffOptions) error {
   120  				opts = o
   121  				return nil
   122  			})
   123  			cmd.PersistentFlags().StringP("repo", "R", "", "")
   124  
   125  			argv, err := shlex.Split(tt.args)
   126  			require.NoError(t, err)
   127  			cmd.SetArgs(argv)
   128  
   129  			cmd.SetIn(&bytes.Buffer{})
   130  			cmd.SetOut(io.Discard)
   131  			cmd.SetErr(io.Discard)
   132  
   133  			_, err = cmd.ExecuteC()
   134  			if tt.wantErr != "" {
   135  				require.EqualError(t, err, tt.wantErr)
   136  				return
   137  			} else {
   138  				require.NoError(t, err)
   139  			}
   140  
   141  			assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg)
   142  			assert.Equal(t, tt.want.UseColor, opts.UseColor)
   143  			assert.Equal(t, tt.want.BrowserMode, opts.BrowserMode)
   144  		})
   145  	}
   146  }
   147  
   148  func Test_diffRun(t *testing.T) {
   149  	pr := &api.PullRequest{Number: 123, URL: "https://github.com/OWNER/REPO/pull/123"}
   150  
   151  	tests := []struct {
   152  		name           string
   153  		opts           DiffOptions
   154  		wantFields     []string
   155  		wantStdout     string
   156  		wantStderr     string
   157  		wantBrowsedURL string
   158  		httpStubs      func(*httpmock.Registry)
   159  	}{
   160  		{
   161  			name: "no color",
   162  			opts: DiffOptions{
   163  				SelectorArg: "123",
   164  				UseColor:    false,
   165  				Patch:       false,
   166  			},
   167  			wantFields: []string{"number"},
   168  			wantStdout: fmt.Sprintf(testDiff, "", "", "", ""),
   169  			httpStubs: func(reg *httpmock.Registry) {
   170  				stubDiffRequest(reg, "application/vnd.github.v3.diff", fmt.Sprintf(testDiff, "", "", "", ""))
   171  			},
   172  		},
   173  		{
   174  			name: "with color",
   175  			opts: DiffOptions{
   176  				SelectorArg: "123",
   177  				UseColor:    true,
   178  				Patch:       false,
   179  			},
   180  			wantFields: []string{"number"},
   181  			wantStdout: fmt.Sprintf(testDiff, "\x1b[m", "\x1b[1;38m", "\x1b[32m", "\x1b[31m"),
   182  			httpStubs: func(reg *httpmock.Registry) {
   183  				stubDiffRequest(reg, "application/vnd.github.v3.diff", fmt.Sprintf(testDiff, "", "", "", ""))
   184  			},
   185  		},
   186  		{
   187  			name: "patch format",
   188  			opts: DiffOptions{
   189  				SelectorArg: "123",
   190  				UseColor:    false,
   191  				Patch:       true,
   192  			},
   193  			wantFields: []string{"number"},
   194  			wantStdout: fmt.Sprintf(testDiff, "", "", "", ""),
   195  			httpStubs: func(reg *httpmock.Registry) {
   196  				stubDiffRequest(reg, "application/vnd.github.v3.patch", fmt.Sprintf(testDiff, "", "", "", ""))
   197  			},
   198  		},
   199  		{
   200  			name: "name only",
   201  			opts: DiffOptions{
   202  				SelectorArg: "123",
   203  				UseColor:    false,
   204  				Patch:       false,
   205  				NameOnly:    true,
   206  			},
   207  			wantFields: []string{"number"},
   208  			wantStdout: ".github/workflows/releases.yml\nMakefile\n",
   209  			httpStubs: func(reg *httpmock.Registry) {
   210  				stubDiffRequest(reg, "application/vnd.github.v3.diff", fmt.Sprintf(testDiff, "", "", "", ""))
   211  			},
   212  		},
   213  		{
   214  			name: "web mode",
   215  			opts: DiffOptions{
   216  				SelectorArg: "123",
   217  				BrowserMode: true,
   218  			},
   219  			wantFields:     []string{"url"},
   220  			wantStderr:     "Opening github.com/OWNER/REPO/pull/123/files in your browser.\n",
   221  			wantBrowsedURL: "https://github.com/OWNER/REPO/pull/123/files",
   222  		},
   223  	}
   224  	for _, tt := range tests {
   225  		t.Run(tt.name, func(t *testing.T) {
   226  			httpReg := &httpmock.Registry{}
   227  			defer httpReg.Verify(t)
   228  			if tt.httpStubs != nil {
   229  				tt.httpStubs(httpReg)
   230  			}
   231  			tt.opts.HttpClient = func() (*http.Client, error) {
   232  				return &http.Client{Transport: httpReg}, nil
   233  			}
   234  
   235  			browser := &browser.Stub{}
   236  			tt.opts.Browser = browser
   237  
   238  			ios, _, stdout, stderr := iostreams.Test()
   239  			ios.SetStdoutTTY(true)
   240  			tt.opts.IO = ios
   241  
   242  			finder := shared.NewMockFinder("123", pr, ghrepo.New("OWNER", "REPO"))
   243  			finder.ExpectFields(tt.wantFields)
   244  			tt.opts.Finder = finder
   245  
   246  			err := diffRun(&tt.opts)
   247  			assert.NoError(t, err)
   248  
   249  			assert.Equal(t, tt.wantStdout, stdout.String())
   250  			assert.Equal(t, tt.wantStderr, stderr.String())
   251  			assert.Equal(t, tt.wantBrowsedURL, browser.BrowsedURL())
   252  		})
   253  	}
   254  }
   255  
   256  const testDiff = `%[2]sdiff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml%[1]s
   257  %[2]sindex 73974448..b7fc0154 100644%[1]s
   258  %[2]s--- a/.github/workflows/releases.yml%[1]s
   259  %[2]s+++ b/.github/workflows/releases.yml%[1]s
   260  @@ -44,6 +44,11 @@ jobs:
   261             token: ${{secrets.SITE_GITHUB_TOKEN}}
   262         - name: Publish documentation site
   263           if: "!contains(github.ref, '-')" # skip prereleases
   264  %[3]s+        env:%[1]s
   265  %[3]s+          GIT_COMMITTER_NAME: cli automation%[1]s
   266  %[3]s+          GIT_AUTHOR_NAME: cli automation%[1]s
   267  %[3]s+          GIT_COMMITTER_EMAIL: noreply@github.com%[1]s
   268  %[3]s+          GIT_AUTHOR_EMAIL: noreply@github.com%[1]s
   269           run: make site-publish
   270         - name: Move project cards
   271           if: "!contains(github.ref, '-')" # skip prereleases
   272  %[2]sdiff --git a/Makefile b/Makefile%[1]s
   273  %[2]sindex f2b4805c..3d7bd0f9 100644%[1]s
   274  %[2]s--- a/Makefile%[1]s
   275  %[2]s+++ b/Makefile%[1]s
   276  @@ -22,8 +22,8 @@ test:
   277   	go test ./...
   278   .PHONY: test
   279  
   280  %[4]s-site:%[1]s
   281  %[4]s-	git clone https://github.com/github/cli.github.com.git "$@"%[1]s
   282  %[3]s+site: bin/gh%[1]s
   283  %[3]s+	bin/gh repo clone github/cli.github.com "$@"%[1]s
   284  
   285   site-docs: site
   286   	git -C site pull
   287  `
   288  
   289  func Test_colorDiffLines(t *testing.T) {
   290  	inputs := []struct {
   291  		input, output string
   292  	}{
   293  		{
   294  			input:  "",
   295  			output: "",
   296  		},
   297  		{
   298  			input:  "\n",
   299  			output: "\n",
   300  		},
   301  		{
   302  			input:  "foo\nbar\nbaz\n",
   303  			output: "foo\nbar\nbaz\n",
   304  		},
   305  		{
   306  			input:  "foo\nbar\nbaz",
   307  			output: "foo\nbar\nbaz\n",
   308  		},
   309  		{
   310  			input: fmt.Sprintf("+foo\n-b%sr\n+++ baz\n", strings.Repeat("a", 2*lineBufferSize)),
   311  			output: fmt.Sprintf(
   312  				"%[4]s+foo%[2]s\n%[5]s-b%[1]sr%[2]s\n%[3]s+++ baz%[2]s\n",
   313  				strings.Repeat("a", 2*lineBufferSize),
   314  				"\x1b[m",
   315  				"\x1b[1;38m",
   316  				"\x1b[32m",
   317  				"\x1b[31m",
   318  			),
   319  		},
   320  	}
   321  	for _, tt := range inputs {
   322  		buf := bytes.Buffer{}
   323  		if err := colorDiffLines(&buf, strings.NewReader(tt.input)); err != nil {
   324  			t.Fatalf("unexpected error: %s", err)
   325  		}
   326  		if got := buf.String(); got != tt.output {
   327  			t.Errorf("expected: %q, got: %q", tt.output, got)
   328  		}
   329  	}
   330  }
   331  
   332  func Test_changedFileNames(t *testing.T) {
   333  	inputs := []struct {
   334  		input, output string
   335  	}{
   336  		{
   337  			input:  "",
   338  			output: "",
   339  		},
   340  		{
   341  			input:  "\n",
   342  			output: "",
   343  		},
   344  		{
   345  			input:  "diff --git a/cmd.go b/cmd.go\n--- /dev/null\n+++ b/cmd.go\n@@ -0,0 +1,313 @@",
   346  			output: "cmd.go\n",
   347  		},
   348  		{
   349  			input:  "diff --git a/cmd.go b/cmd.go\n--- a/cmd.go\n+++ /dev/null\n@@ -0,0 +1,313 @@",
   350  			output: "cmd.go\n",
   351  		},
   352  		{
   353  			input:  fmt.Sprintf("diff --git a/baz.go b/rename.go\n--- a/baz.go\n+++ b/rename.go\n+foo\n-b%sr", strings.Repeat("a", 2*lineBufferSize)),
   354  			output: "rename.go\n",
   355  		},
   356  		{
   357  			input:  fmt.Sprintf("diff --git a/baz.go b/baz.go\n--- a/baz.go\n+++ b/baz.go\n+foo\n-b%sr", strings.Repeat("a", 2*lineBufferSize)),
   358  			output: "baz.go\n",
   359  		},
   360  	}
   361  	for _, tt := range inputs {
   362  		buf := bytes.Buffer{}
   363  		if err := changedFilesNames(&buf, strings.NewReader(tt.input)); err != nil {
   364  			t.Fatalf("unexpected error: %s", err)
   365  		}
   366  		if got := buf.String(); got != tt.output {
   367  			t.Errorf("expected: %q, got: %q", tt.output, got)
   368  		}
   369  	}
   370  }
   371  
   372  func stubDiffRequest(reg *httpmock.Registry, accept, diff string) {
   373  	reg.Register(
   374  		func(req *http.Request) bool {
   375  			if !strings.EqualFold(req.Method, "GET") {
   376  				return false
   377  			}
   378  			if req.URL.EscapedPath() != "/repos/OWNER/REPO/pulls/123" {
   379  				return false
   380  			}
   381  			return req.Header.Get("Accept") == accept
   382  		},
   383  		func(req *http.Request) (*http.Response, error) {
   384  			return &http.Response{
   385  				StatusCode: 200,
   386  				Request:    req,
   387  				Body:       io.NopCloser(strings.NewReader(diff)),
   388  			}, nil
   389  		})
   390  }