github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/release/edit/edit_test.go (about)

     1  package edit
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    12  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    13  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    14  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    15  	"github.com/google/shlex"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func Test_NewCmdEdit(t *testing.T) {
    21  	tempDir := t.TempDir()
    22  	tf, err := os.CreateTemp(tempDir, "release-create")
    23  	require.NoError(t, err)
    24  	fmt.Fprint(tf, "MY NOTES")
    25  	tf.Close()
    26  
    27  	tests := []struct {
    28  		name    string
    29  		args    string
    30  		isTTY   bool
    31  		stdin   string
    32  		want    EditOptions
    33  		wantErr string
    34  	}{
    35  		{
    36  			name:    "no arguments notty",
    37  			args:    "",
    38  			isTTY:   false,
    39  			wantErr: "accepts 1 arg(s), received 0",
    40  		},
    41  		{
    42  			name:  "provide title and notes",
    43  			args:  "v1.2.3 --title 'Some Title' --notes 'Some Notes'",
    44  			isTTY: false,
    45  			want: EditOptions{
    46  				TagName: "",
    47  				Name:    stringPtr("Some Title"),
    48  				Body:    stringPtr("Some Notes"),
    49  			},
    50  		},
    51  		{
    52  			name:  "provide discussion category",
    53  			args:  "v1.2.3 --discussion-category some-category",
    54  			isTTY: false,
    55  			want: EditOptions{
    56  				TagName:            "",
    57  				DiscussionCategory: stringPtr("some-category"),
    58  			},
    59  		},
    60  		{
    61  			name:  "provide tag and target commitish",
    62  			args:  "v1.2.3 --tag v9.8.7 --target 97ea5e77b4d61d5d80ed08f7512847dee3ec9af5",
    63  			isTTY: false,
    64  			want: EditOptions{
    65  				TagName: "v9.8.7",
    66  				Target:  "97ea5e77b4d61d5d80ed08f7512847dee3ec9af5",
    67  			},
    68  		},
    69  		{
    70  			name:  "provide prerelease",
    71  			args:  "v1.2.3 --prerelease",
    72  			isTTY: false,
    73  			want: EditOptions{
    74  				TagName:    "",
    75  				Prerelease: boolPtr(true),
    76  			},
    77  		},
    78  		{
    79  			name:  "provide prerelease=false",
    80  			args:  "v1.2.3 --prerelease=false",
    81  			isTTY: false,
    82  			want: EditOptions{
    83  				TagName:    "",
    84  				Prerelease: boolPtr(false),
    85  			},
    86  		},
    87  		{
    88  			name:  "provide draft",
    89  			args:  "v1.2.3 --draft",
    90  			isTTY: false,
    91  			want: EditOptions{
    92  				TagName: "",
    93  				Draft:   boolPtr(true),
    94  			},
    95  		},
    96  		{
    97  			name:  "provide draft=false",
    98  			args:  "v1.2.3 --draft=false",
    99  			isTTY: false,
   100  			want: EditOptions{
   101  				TagName: "",
   102  				Draft:   boolPtr(false),
   103  			},
   104  		},
   105  		{
   106  			name:  "latest",
   107  			args:  "v1.2.3 --latest",
   108  			isTTY: false,
   109  			want: EditOptions{
   110  				TagName:  "",
   111  				IsLatest: boolPtr(true),
   112  			},
   113  		},
   114  		{
   115  			name:  "not latest",
   116  			args:  "v1.2.3 --latest=false",
   117  			isTTY: false,
   118  			want: EditOptions{
   119  				TagName:  "",
   120  				IsLatest: boolPtr(false),
   121  			},
   122  		},
   123  		{
   124  			name:  "provide notes from file",
   125  			args:  fmt.Sprintf(`v1.2.3 -F '%s'`, tf.Name()),
   126  			isTTY: false,
   127  			want: EditOptions{
   128  				TagName: "",
   129  				Body:    stringPtr("MY NOTES"),
   130  			},
   131  		},
   132  		{
   133  			name:  "provide notes from stdin",
   134  			args:  "v1.2.3 -F -",
   135  			isTTY: false,
   136  			stdin: "MY NOTES",
   137  			want: EditOptions{
   138  				TagName: "",
   139  				Body:    stringPtr("MY NOTES"),
   140  			},
   141  		},
   142  	}
   143  
   144  	for _, tt := range tests {
   145  		t.Run(tt.name, func(t *testing.T) {
   146  			ios, stdin, _, _ := iostreams.Test()
   147  			if tt.stdin == "" {
   148  				ios.SetStdinTTY(tt.isTTY)
   149  			} else {
   150  				ios.SetStdinTTY(false)
   151  				fmt.Fprint(stdin, tt.stdin)
   152  			}
   153  			ios.SetStdoutTTY(tt.isTTY)
   154  			ios.SetStderrTTY(tt.isTTY)
   155  
   156  			f := &cmdutil.Factory{
   157  				IOStreams: ios,
   158  			}
   159  
   160  			var opts *EditOptions
   161  			cmd := NewCmdEdit(f, func(o *EditOptions) error {
   162  				opts = o
   163  				return nil
   164  			})
   165  			cmd.PersistentFlags().StringP("repo", "R", "", "")
   166  
   167  			argv, err := shlex.Split(tt.args)
   168  			require.NoError(t, err)
   169  			cmd.SetArgs(argv)
   170  
   171  			cmd.SetIn(&bytes.Buffer{})
   172  			cmd.SetOut(io.Discard)
   173  			cmd.SetErr(io.Discard)
   174  
   175  			_, err = cmd.ExecuteC()
   176  			if tt.wantErr != "" {
   177  				require.EqualError(t, err, tt.wantErr)
   178  				return
   179  			} else {
   180  				require.NoError(t, err)
   181  			}
   182  
   183  			assert.Equal(t, tt.want.TagName, opts.TagName)
   184  			assert.Equal(t, tt.want.Target, opts.Target)
   185  			assert.Equal(t, tt.want.Name, opts.Name)
   186  			assert.Equal(t, tt.want.Body, opts.Body)
   187  			assert.Equal(t, tt.want.DiscussionCategory, opts.DiscussionCategory)
   188  			assert.Equal(t, tt.want.Draft, opts.Draft)
   189  			assert.Equal(t, tt.want.Prerelease, opts.Prerelease)
   190  			assert.Equal(t, tt.want.IsLatest, opts.IsLatest)
   191  		})
   192  	}
   193  }
   194  
   195  func Test_editRun(t *testing.T) {
   196  	tests := []struct {
   197  		name       string
   198  		isTTY      bool
   199  		opts       EditOptions
   200  		httpStubs  func(t *testing.T, reg *httpmock.Registry)
   201  		wantErr    string
   202  		wantStdout string
   203  		wantStderr string
   204  	}{
   205  		{
   206  			name:  "edit the tag name",
   207  			isTTY: true,
   208  			opts: EditOptions{
   209  				TagName: "v1.2.4",
   210  			},
   211  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   212  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   213  					assert.Equal(t, map[string]interface{}{
   214  						"tag_name": "v1.2.4",
   215  					}, params)
   216  				})
   217  			},
   218  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   219  			wantStderr: "",
   220  		},
   221  		{
   222  			name:  "edit the target",
   223  			isTTY: true,
   224  			opts: EditOptions{
   225  				Target: "c0ff33",
   226  			},
   227  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   228  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   229  					assert.Equal(t, map[string]interface{}{
   230  						"tag_name":         "v1.2.3",
   231  						"target_commitish": "c0ff33",
   232  					}, params)
   233  				})
   234  			},
   235  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   236  			wantStderr: "",
   237  		},
   238  		{
   239  			name:  "edit the release name",
   240  			isTTY: true,
   241  			opts: EditOptions{
   242  				Name: stringPtr("Hot Release #1"),
   243  			},
   244  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   245  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   246  					assert.Equal(t, map[string]interface{}{
   247  						"tag_name": "v1.2.3",
   248  						"name":     "Hot Release #1",
   249  					}, params)
   250  				})
   251  			},
   252  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   253  			wantStderr: "",
   254  		},
   255  		{
   256  			name:  "edit the discussion category",
   257  			isTTY: true,
   258  			opts: EditOptions{
   259  				DiscussionCategory: stringPtr("some-category"),
   260  			},
   261  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   262  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   263  					assert.Equal(t, map[string]interface{}{
   264  						"tag_name":                 "v1.2.3",
   265  						"discussion_category_name": "some-category",
   266  					}, params)
   267  				})
   268  			},
   269  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   270  			wantStderr: "",
   271  		},
   272  		{
   273  			name:  "edit the latest marker",
   274  			isTTY: false,
   275  			opts: EditOptions{
   276  				IsLatest: boolPtr(true),
   277  			},
   278  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   279  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   280  					assert.Equal(t, map[string]interface{}{
   281  						"tag_name":    "v1.2.3",
   282  						"make_latest": "true",
   283  					}, params)
   284  				})
   285  			},
   286  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   287  			wantStderr: "",
   288  		},
   289  		{
   290  			name:  "edit the release name (empty)",
   291  			isTTY: true,
   292  			opts: EditOptions{
   293  				Name: stringPtr(""),
   294  			},
   295  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   296  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   297  					assert.Equal(t, map[string]interface{}{
   298  						"tag_name": "v1.2.3",
   299  						"name":     "",
   300  					}, params)
   301  				})
   302  			},
   303  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   304  			wantStderr: "",
   305  		},
   306  		{
   307  			name:  "edit the release notes",
   308  			isTTY: true,
   309  			opts: EditOptions{
   310  				Body: stringPtr("Release Notes:\n- Fix Bug #1\n- Fix Bug #2"),
   311  			},
   312  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   313  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   314  					assert.Equal(t, map[string]interface{}{
   315  						"tag_name": "v1.2.3",
   316  						"body":     "Release Notes:\n- Fix Bug #1\n- Fix Bug #2",
   317  					}, params)
   318  				})
   319  			},
   320  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   321  			wantStderr: "",
   322  		},
   323  		{
   324  			name:  "edit the release notes (empty)",
   325  			isTTY: true,
   326  			opts: EditOptions{
   327  				Body: stringPtr(""),
   328  			},
   329  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   330  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   331  					assert.Equal(t, map[string]interface{}{
   332  						"tag_name": "v1.2.3",
   333  						"body":     "",
   334  					}, params)
   335  				})
   336  			},
   337  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   338  			wantStderr: "",
   339  		},
   340  		{
   341  			name:  "edit draft (true)",
   342  			isTTY: true,
   343  			opts: EditOptions{
   344  				Draft: boolPtr(true),
   345  			},
   346  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   347  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   348  					assert.Equal(t, map[string]interface{}{
   349  						"tag_name": "v1.2.3",
   350  						"draft":    true,
   351  					}, params)
   352  				})
   353  			},
   354  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   355  			wantStderr: "",
   356  		},
   357  		{
   358  			name:  "edit draft (false)",
   359  			isTTY: true,
   360  			opts: EditOptions{
   361  				Draft: boolPtr(false),
   362  			},
   363  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   364  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   365  					assert.Equal(t, map[string]interface{}{
   366  						"tag_name": "v1.2.3",
   367  						"draft":    false,
   368  					}, params)
   369  				})
   370  			},
   371  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   372  			wantStderr: "",
   373  		},
   374  		{
   375  			name:  "edit prerelease (true)",
   376  			isTTY: true,
   377  			opts: EditOptions{
   378  				Prerelease: boolPtr(true),
   379  			},
   380  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   381  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   382  					assert.Equal(t, map[string]interface{}{
   383  						"tag_name":   "v1.2.3",
   384  						"prerelease": true,
   385  					}, params)
   386  				})
   387  			},
   388  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   389  			wantStderr: "",
   390  		},
   391  		{
   392  			name:  "edit prerelease (false)",
   393  			isTTY: true,
   394  			opts: EditOptions{
   395  				Prerelease: boolPtr(false),
   396  			},
   397  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   398  				mockSuccessfulEditResponse(reg, func(params map[string]interface{}) {
   399  					assert.Equal(t, map[string]interface{}{
   400  						"tag_name":   "v1.2.3",
   401  						"prerelease": false,
   402  					}, params)
   403  				})
   404  			},
   405  			wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n",
   406  			wantStderr: "",
   407  		},
   408  	}
   409  
   410  	for _, tt := range tests {
   411  		t.Run(tt.name, func(t *testing.T) {
   412  			ios, _, stdout, stderr := iostreams.Test()
   413  			ios.SetStdoutTTY(tt.isTTY)
   414  			ios.SetStdinTTY(tt.isTTY)
   415  			ios.SetStderrTTY(tt.isTTY)
   416  
   417  			fakeHTTP := &httpmock.Registry{}
   418  			fakeHTTP.Register(httpmock.REST("GET", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.JSONResponse(map[string]interface{}{
   419  				"id":       12345,
   420  				"tag_name": "v1.2.3",
   421  			}))
   422  			if tt.httpStubs != nil {
   423  				tt.httpStubs(t, fakeHTTP)
   424  			}
   425  			defer fakeHTTP.Verify(t)
   426  
   427  			tt.opts.IO = ios
   428  			tt.opts.HttpClient = func() (*http.Client, error) {
   429  				return &http.Client{Transport: fakeHTTP}, nil
   430  			}
   431  			tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   432  				return ghrepo.FromFullName("OWNER/REPO")
   433  			}
   434  
   435  			err := editRun("v1.2.3", &tt.opts)
   436  			if tt.wantErr != "" {
   437  				require.EqualError(t, err, tt.wantErr)
   438  				return
   439  			} else {
   440  				require.NoError(t, err)
   441  			}
   442  
   443  			assert.Equal(t, tt.wantStdout, stdout.String())
   444  			assert.Equal(t, tt.wantStderr, stderr.String())
   445  		})
   446  	}
   447  }
   448  
   449  func mockSuccessfulEditResponse(reg *httpmock.Registry, cb func(params map[string]interface{})) {
   450  	matcher := httpmock.REST("PATCH", "repos/OWNER/REPO/releases/12345")
   451  	responder := httpmock.RESTPayload(201, `{
   452  		"html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3"
   453  	}`, cb)
   454  	reg.Register(matcher, responder)
   455  }
   456  
   457  func boolPtr(b bool) *bool {
   458  	return &b
   459  }
   460  
   461  func stringPtr(s string) *string {
   462  	return &s
   463  }