github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/secret/set/set_test.go (about)

     1  package set
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"testing"
    10  
    11  	"github.com/cli/cli/internal/config"
    12  	"github.com/cli/cli/internal/ghrepo"
    13  	"github.com/cli/cli/pkg/cmd/secret/shared"
    14  	"github.com/cli/cli/pkg/cmdutil"
    15  	"github.com/cli/cli/pkg/httpmock"
    16  	"github.com/cli/cli/pkg/iostreams"
    17  	"github.com/cli/cli/pkg/prompt"
    18  	"github.com/google/shlex"
    19  	"github.com/stretchr/testify/assert"
    20  )
    21  
    22  func TestNewCmdSet(t *testing.T) {
    23  	tests := []struct {
    24  		name     string
    25  		cli      string
    26  		wants    SetOptions
    27  		stdinTTY bool
    28  		wantsErr bool
    29  	}{
    30  		{
    31  			name:     "invalid visibility",
    32  			cli:      "cool_secret --org coolOrg -v'mistyVeil'",
    33  			wantsErr: true,
    34  		},
    35  		{
    36  			name:     "invalid visibility",
    37  			cli:      "cool_secret --org coolOrg -v'selected'",
    38  			wantsErr: true,
    39  		},
    40  		{
    41  			name:     "repos with wrong vis",
    42  			cli:      "cool_secret --org coolOrg -v'private' -rcoolRepo",
    43  			wantsErr: true,
    44  		},
    45  		{
    46  			name:     "no name",
    47  			cli:      "",
    48  			wantsErr: true,
    49  		},
    50  		{
    51  			name:     "multiple names",
    52  			cli:      "cool_secret good_secret",
    53  			wantsErr: true,
    54  		},
    55  		{
    56  			name:     "visibility without org",
    57  			cli:      "cool_secret -vall",
    58  			wantsErr: true,
    59  		},
    60  		{
    61  			name: "repos without vis",
    62  			cli:  "cool_secret -bs --org coolOrg -rcoolRepo",
    63  			wants: SetOptions{
    64  				SecretName:      "cool_secret",
    65  				Visibility:      shared.Selected,
    66  				RepositoryNames: []string{"coolRepo"},
    67  				Body:            "s",
    68  				OrgName:         "coolOrg",
    69  			},
    70  		},
    71  		{
    72  			name: "org with selected repo",
    73  			cli:  "-ocoolOrg -bs -vselected -rcoolRepo cool_secret",
    74  			wants: SetOptions{
    75  				SecretName:      "cool_secret",
    76  				Visibility:      shared.Selected,
    77  				RepositoryNames: []string{"coolRepo"},
    78  				Body:            "s",
    79  				OrgName:         "coolOrg",
    80  			},
    81  		},
    82  		{
    83  			name: "org with selected repos",
    84  			cli:  `--org=coolOrg -bs -vselected -r="coolRepo,radRepo,goodRepo" cool_secret`,
    85  			wants: SetOptions{
    86  				SecretName:      "cool_secret",
    87  				Visibility:      shared.Selected,
    88  				RepositoryNames: []string{"coolRepo", "goodRepo", "radRepo"},
    89  				Body:            "s",
    90  				OrgName:         "coolOrg",
    91  			},
    92  		},
    93  		{
    94  			name: "repo",
    95  			cli:  `cool_secret -b"a secret"`,
    96  			wants: SetOptions{
    97  				SecretName: "cool_secret",
    98  				Visibility: shared.Private,
    99  				Body:       "a secret",
   100  				OrgName:    "",
   101  			},
   102  		},
   103  		{
   104  			name: "env",
   105  			cli:  `cool_secret -b"a secret" -eRelease`,
   106  			wants: SetOptions{
   107  				SecretName: "cool_secret",
   108  				Visibility: shared.Private,
   109  				Body:       "a secret",
   110  				OrgName:    "",
   111  				EnvName:    "Release",
   112  			},
   113  		},
   114  		{
   115  			name: "vis all",
   116  			cli:  `cool_secret --org coolOrg -b"cool" -vall`,
   117  			wants: SetOptions{
   118  				SecretName: "cool_secret",
   119  				Visibility: shared.All,
   120  				Body:       "cool",
   121  				OrgName:    "coolOrg",
   122  			},
   123  		},
   124  		{
   125  			name:     "bad name prefix",
   126  			cli:      `GITHUB_SECRET -b"cool"`,
   127  			wantsErr: true,
   128  		},
   129  		{
   130  			name:     "leading numbers in name",
   131  			cli:      `123_SECRET -b"cool"`,
   132  			wantsErr: true,
   133  		},
   134  		{
   135  			name:     "invalid characters in name",
   136  			cli:      `BAD-SECRET -b"cool"`,
   137  			wantsErr: true,
   138  		},
   139  	}
   140  
   141  	for _, tt := range tests {
   142  		t.Run(tt.name, func(t *testing.T) {
   143  			io, _, _, _ := iostreams.Test()
   144  			f := &cmdutil.Factory{
   145  				IOStreams: io,
   146  			}
   147  
   148  			io.SetStdinTTY(tt.stdinTTY)
   149  
   150  			argv, err := shlex.Split(tt.cli)
   151  			assert.NoError(t, err)
   152  
   153  			var gotOpts *SetOptions
   154  			cmd := NewCmdSet(f, func(opts *SetOptions) error {
   155  				gotOpts = opts
   156  				return nil
   157  			})
   158  			cmd.SetArgs(argv)
   159  			cmd.SetIn(&bytes.Buffer{})
   160  			cmd.SetOut(&bytes.Buffer{})
   161  			cmd.SetErr(&bytes.Buffer{})
   162  
   163  			_, err = cmd.ExecuteC()
   164  			if tt.wantsErr {
   165  				assert.Error(t, err)
   166  				return
   167  			}
   168  			assert.NoError(t, err)
   169  
   170  			assert.Equal(t, tt.wants.SecretName, gotOpts.SecretName)
   171  			assert.Equal(t, tt.wants.Body, gotOpts.Body)
   172  			assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility)
   173  			assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
   174  			assert.Equal(t, tt.wants.EnvName, gotOpts.EnvName)
   175  			assert.ElementsMatch(t, tt.wants.RepositoryNames, gotOpts.RepositoryNames)
   176  		})
   177  	}
   178  }
   179  
   180  func Test_setRun_repo(t *testing.T) {
   181  	reg := &httpmock.Registry{}
   182  
   183  	reg.Register(httpmock.REST("GET", "repos/owner/repo/actions/secrets/public-key"),
   184  		httpmock.JSONResponse(PubKey{ID: "123", Key: "CDjXqf7AJBXWhMczcy+Fs7JlACEptgceysutztHaFQI="}))
   185  
   186  	reg.Register(httpmock.REST("PUT", "repos/owner/repo/actions/secrets/cool_secret"), httpmock.StatusStringResponse(201, `{}`))
   187  
   188  	io, _, _, _ := iostreams.Test()
   189  
   190  	opts := &SetOptions{
   191  		HttpClient: func() (*http.Client, error) {
   192  			return &http.Client{Transport: reg}, nil
   193  		},
   194  		Config: func() (config.Config, error) { return config.NewBlankConfig(), nil },
   195  		BaseRepo: func() (ghrepo.Interface, error) {
   196  			return ghrepo.FromFullName("owner/repo")
   197  		},
   198  		IO:         io,
   199  		SecretName: "cool_secret",
   200  		Body:       "a secret",
   201  		// Cribbed from https://github.com/golang/crypto/commit/becbf705a91575484002d598f87d74f0002801e7
   202  		RandomOverride: bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}),
   203  	}
   204  
   205  	err := setRun(opts)
   206  	assert.NoError(t, err)
   207  
   208  	reg.Verify(t)
   209  
   210  	data, err := ioutil.ReadAll(reg.Requests[1].Body)
   211  	assert.NoError(t, err)
   212  	var payload SecretPayload
   213  	err = json.Unmarshal(data, &payload)
   214  	assert.NoError(t, err)
   215  	assert.Equal(t, payload.KeyID, "123")
   216  	assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
   217  }
   218  
   219  func Test_setRun_env(t *testing.T) {
   220  	reg := &httpmock.Registry{}
   221  
   222  	reg.Register(httpmock.REST("GET", "repos/owner/repo/environments/development/secrets/public-key"),
   223  		httpmock.JSONResponse(PubKey{ID: "123", Key: "CDjXqf7AJBXWhMczcy+Fs7JlACEptgceysutztHaFQI="}))
   224  
   225  	reg.Register(httpmock.REST("PUT", "repos/owner/repo/environments/development/secrets/cool_secret"), httpmock.StatusStringResponse(201, `{}`))
   226  
   227  	io, _, _, _ := iostreams.Test()
   228  
   229  	opts := &SetOptions{
   230  		HttpClient: func() (*http.Client, error) {
   231  			return &http.Client{Transport: reg}, nil
   232  		},
   233  		Config: func() (config.Config, error) { return config.NewBlankConfig(), nil },
   234  		BaseRepo: func() (ghrepo.Interface, error) {
   235  			return ghrepo.FromFullName("owner/repo")
   236  		},
   237  		EnvName:    "development",
   238  		IO:         io,
   239  		SecretName: "cool_secret",
   240  		Body:       "a secret",
   241  		// Cribbed from https://github.com/golang/crypto/commit/becbf705a91575484002d598f87d74f0002801e7
   242  		RandomOverride: bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}),
   243  	}
   244  
   245  	err := setRun(opts)
   246  	assert.NoError(t, err)
   247  
   248  	reg.Verify(t)
   249  
   250  	data, err := ioutil.ReadAll(reg.Requests[1].Body)
   251  	assert.NoError(t, err)
   252  	var payload SecretPayload
   253  	err = json.Unmarshal(data, &payload)
   254  	assert.NoError(t, err)
   255  	assert.Equal(t, payload.KeyID, "123")
   256  	assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
   257  }
   258  
   259  func Test_setRun_org(t *testing.T) {
   260  	tests := []struct {
   261  		name             string
   262  		opts             *SetOptions
   263  		wantVisibility   shared.Visibility
   264  		wantRepositories []int
   265  	}{
   266  		{
   267  			name: "all vis",
   268  			opts: &SetOptions{
   269  				OrgName:    "UmbrellaCorporation",
   270  				Visibility: shared.All,
   271  			},
   272  		},
   273  		{
   274  			name: "selected visibility",
   275  			opts: &SetOptions{
   276  				OrgName:         "UmbrellaCorporation",
   277  				Visibility:      shared.Selected,
   278  				RepositoryNames: []string{"birkin", "wesker"},
   279  			},
   280  			wantRepositories: []int{1, 2},
   281  		},
   282  	}
   283  
   284  	for _, tt := range tests {
   285  		t.Run(tt.name, func(t *testing.T) {
   286  			reg := &httpmock.Registry{}
   287  
   288  			orgName := tt.opts.OrgName
   289  
   290  			reg.Register(httpmock.REST("GET",
   291  				fmt.Sprintf("orgs/%s/actions/secrets/public-key", orgName)),
   292  				httpmock.JSONResponse(PubKey{ID: "123", Key: "CDjXqf7AJBXWhMczcy+Fs7JlACEptgceysutztHaFQI="}))
   293  
   294  			reg.Register(httpmock.REST("PUT",
   295  				fmt.Sprintf("orgs/%s/actions/secrets/cool_secret", orgName)),
   296  				httpmock.StatusStringResponse(201, `{}`))
   297  
   298  			if len(tt.opts.RepositoryNames) > 0 {
   299  				reg.Register(httpmock.GraphQL(`query MapRepositoryNames\b`),
   300  					httpmock.StringResponse(`{"data":{"birkin":{"databaseId":1},"wesker":{"databaseId":2}}}`))
   301  			}
   302  
   303  			io, _, _, _ := iostreams.Test()
   304  
   305  			tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   306  				return ghrepo.FromFullName("owner/repo")
   307  			}
   308  			tt.opts.HttpClient = func() (*http.Client, error) {
   309  				return &http.Client{Transport: reg}, nil
   310  			}
   311  			tt.opts.Config = func() (config.Config, error) {
   312  				return config.NewBlankConfig(), nil
   313  			}
   314  			tt.opts.IO = io
   315  			tt.opts.SecretName = "cool_secret"
   316  			tt.opts.Body = "a secret"
   317  			// Cribbed from https://github.com/golang/crypto/commit/becbf705a91575484002d598f87d74f0002801e7
   318  			tt.opts.RandomOverride = bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5})
   319  
   320  			err := setRun(tt.opts)
   321  			assert.NoError(t, err)
   322  
   323  			reg.Verify(t)
   324  
   325  			data, err := ioutil.ReadAll(reg.Requests[len(reg.Requests)-1].Body)
   326  			assert.NoError(t, err)
   327  			var payload SecretPayload
   328  			err = json.Unmarshal(data, &payload)
   329  			assert.NoError(t, err)
   330  			assert.Equal(t, payload.KeyID, "123")
   331  			assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
   332  			assert.Equal(t, payload.Visibility, tt.opts.Visibility)
   333  			assert.ElementsMatch(t, payload.Repositories, tt.wantRepositories)
   334  		})
   335  	}
   336  }
   337  
   338  func Test_getBody(t *testing.T) {
   339  	tests := []struct {
   340  		name    string
   341  		bodyArg string
   342  		want    string
   343  		stdin   string
   344  	}{
   345  		{
   346  			name:    "literal value",
   347  			bodyArg: "a secret",
   348  			want:    "a secret",
   349  		},
   350  		{
   351  			name:  "from stdin",
   352  			want:  "a secret",
   353  			stdin: "a secret",
   354  		},
   355  	}
   356  
   357  	for _, tt := range tests {
   358  		t.Run(tt.name, func(t *testing.T) {
   359  			io, stdin, _, _ := iostreams.Test()
   360  
   361  			io.SetStdinTTY(false)
   362  
   363  			_, err := stdin.WriteString(tt.stdin)
   364  			assert.NoError(t, err)
   365  
   366  			body, err := getBody(&SetOptions{
   367  				Body: tt.bodyArg,
   368  				IO:   io,
   369  			})
   370  			assert.NoError(t, err)
   371  
   372  			assert.Equal(t, string(body), tt.want)
   373  		})
   374  	}
   375  }
   376  
   377  func Test_getBodyPrompt(t *testing.T) {
   378  	io, _, _, _ := iostreams.Test()
   379  
   380  	io.SetStdinTTY(true)
   381  	io.SetStdoutTTY(true)
   382  
   383  	as, teardown := prompt.InitAskStubber()
   384  	defer teardown()
   385  
   386  	as.StubOne("cool secret")
   387  
   388  	body, err := getBody(&SetOptions{
   389  		IO: io,
   390  	})
   391  	assert.NoError(t, err)
   392  	assert.Equal(t, string(body), "cool secret")
   393  }