Add tests for secret commands

Co-authored-by: William Martin <williammartin@github.com>
This commit is contained in:
Wing 2024-12-04 15:25:47 +01:00 committed by William Martin
parent 88988374b5
commit 57c9ee0ad2
4 changed files with 427 additions and 1 deletions

View file

@ -5,6 +5,8 @@ import (
"net/http"
"testing"
ghContext "github.com/cli/cli/v2/context"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/gh"
"github.com/cli/cli/v2/internal/ghrepo"
@ -351,3 +353,119 @@ func Test_removeRun_user(t *testing.T) {
reg.Verify(t)
}
func Test_removeRun_remote_validation(t *testing.T) {
tests := []struct {
name string
opts *DeleteOptions
wantPath string
wantErr bool
errMsg string
}{
{
name: "single repo detected",
opts: &DeleteOptions{
Application: "actions",
SecretName: "cool_secret",
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
}, nil
}},
wantPath: "repos/owner/repo/actions/secrets/cool_secret",
},
{
name: "multi repo detected",
opts: &DeleteOptions{
Application: "actions",
SecretName: "cool_secret",
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
}},
wantErr: true,
errMsg: "multiple remotes detected [origin upstream]. please specify which repo to use by providing the -R or --repo argument",
},
{
name: "multi repo detected - single repo given",
opts: &DeleteOptions{
Application: "actions",
SecretName: "cool_secret",
HasRepoOverride: true,
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
}},
wantPath: "repos/owner/repo/actions/secrets/cool_secret",
},
}
for _, tt := range tests {
reg := &httpmock.Registry{}
if tt.wantPath != "" {
reg.Register(
httpmock.REST("DELETE", tt.wantPath),
httpmock.StatusStringResponse(204, "No Content"))
}
ios, _, _, _ := iostreams.Test()
tt.opts.IO = ios
tt.opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: reg}, nil
}
tt.opts.Config = func() (gh.Config, error) {
return config.NewBlankConfig(), nil
}
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
}
err := removeRun(tt.opts)
if tt.wantErr {
assert.EqualError(t, err, tt.errMsg)
} else {
assert.NoError(t, err)
}
reg.Verify(t)
}
}

View file

@ -9,6 +9,8 @@ import (
"testing"
"time"
ghContext "github.com/cli/cli/v2/context"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/gh"
"github.com/cli/cli/v2/internal/ghrepo"
@ -442,6 +444,173 @@ func Test_listRun(t *testing.T) {
}
}
func Test_listRunRemoteValidation(t *testing.T) {
tests := []struct {
name string
tty bool
json bool
opts *ListOptions
wantOut []string
wantErr bool
errMsg string
}{
{
name: "single repo detected",
tty: false,
opts: &ListOptions{
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
}, nil
},
},
wantOut: []string{
"SECRET_ONE\t1988-10-11T00:00:00Z",
"SECRET_TWO\t2020-12-04T00:00:00Z",
"SECRET_THREE\t1975-11-30T00:00:00Z",
},
},
{
name: "multi repo detected",
tty: false,
opts: &ListOptions{
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
},
},
wantOut: []string{},
wantErr: true,
errMsg: "multiple remotes detected [origin upstream]. please specify which repo to use by providing the -R or --repo argument",
},
{
name: "multi repo detected - single repo given",
tty: false,
opts: &ListOptions{
HasRepoOverride: true,
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
},
},
wantOut: []string{
"SECRET_ONE\t1988-10-11T00:00:00Z",
"SECRET_TWO\t2020-12-04T00:00:00Z",
"SECRET_THREE\t1975-11-30T00:00:00Z",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reg := &httpmock.Registry{}
reg.Verify(t)
path := "repos/owner/repo/actions/secrets"
t0, _ := time.Parse("2006-01-02", "1988-10-11")
t1, _ := time.Parse("2006-01-02", "2020-12-04")
t2, _ := time.Parse("2006-01-02", "1975-11-30")
payload := struct {
Secrets []Secret
}{
Secrets: []Secret{
{
Name: "SECRET_ONE",
UpdatedAt: t0,
},
{
Name: "SECRET_TWO",
UpdatedAt: t1,
},
{
Name: "SECRET_THREE",
UpdatedAt: t2,
},
},
}
reg.Register(httpmock.REST("GET", path), httpmock.JSONResponse(payload))
ios, _, stdout, _ := iostreams.Test()
ios.SetStdoutTTY(tt.tty)
tt.opts.IO = ios
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
}
tt.opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: reg}, nil
}
tt.opts.Config = func() (gh.Config, error) {
return config.NewBlankConfig(), nil
}
tt.opts.Now = func() time.Time {
t, _ := time.Parse(time.RFC822, "15 Mar 23 00:00 UTC")
return t
}
if tt.json {
exporter := cmdutil.NewJSONExporter()
exporter.SetFields(secretFields)
tt.opts.Exporter = exporter
}
err := listRun(tt.opts)
if tt.wantErr {
assert.EqualError(t, err, tt.errMsg)
return
}
assert.NoError(t, err)
if len(tt.wantOut) > 1 {
expected := fmt.Sprintf("%s\n", strings.Join(tt.wantOut, "\n"))
assert.Equal(t, expected, stdout.String())
}
})
}
}
// Test_listRun_populatesNumSelectedReposIfRequired asserts that NumSelectedRepos
// field is populated **only** when it's going to be presented in the output. Since
// populating this field costs further API requests (one per secret), it's important

View file

@ -10,6 +10,8 @@ import (
"testing"
"github.com/MakeNowJust/heredoc"
ghContext "github.com/cli/cli/v2/context"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/gh"
"github.com/cli/cli/v2/internal/ghrepo"
@ -700,6 +702,143 @@ func Test_getSecretsFromOptions(t *testing.T) {
}
}
func Test_setRun_remote_validation(t *testing.T) {
tests := []struct {
name string
opts *SetOptions
wantApp string
wantErr bool
errMsg string
}{
{
name: "single repo detected",
opts: &SetOptions{
Application: "actions",
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
}, nil
},
},
wantApp: "actions",
},
{
name: "multi repo detected",
opts: &SetOptions{
Application: "actions",
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
},
},
wantErr: true,
errMsg: "multiple remotes detected [origin upstream]. please specify which repo to use by providing the -R or --repo argument",
},
{
name: "multi repo detected - single repo given",
opts: &SetOptions{
Application: "actions",
HasRepoOverride: true,
Remotes: func() (ghContext.Remotes, error) {
remote := &ghContext.Remote{
Remote: &git.Remote{
Name: "origin",
},
Repo: ghrepo.New("owner", "repo"),
}
remote2 := &ghContext.Remote{
Remote: &git.Remote{
Name: "upstream",
},
Repo: ghrepo.New("owner", "repo"),
}
return ghContext.Remotes{
remote,
remote2,
}, nil
},
},
wantApp: "actions",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reg := &httpmock.Registry{}
if tt.wantApp != "" {
reg.Register(httpmock.REST("GET", fmt.Sprintf("repos/owner/repo/%s/secrets/public-key", tt.wantApp)),
httpmock.JSONResponse(PubKey{ID: "123", Key: "CDjXqf7AJBXWhMczcy+Fs7JlACEptgceysutztHaFQI="}))
reg.Register(httpmock.REST("PUT", fmt.Sprintf("repos/owner/repo/%s/secrets/cool_secret", tt.wantApp)),
httpmock.StatusStringResponse(201, `{}`))
}
ios, _, _, _ := iostreams.Test()
opts := &SetOptions{
HttpClient: func() (*http.Client, error) {
return &http.Client{Transport: reg}, nil
},
Config: func() (gh.Config, error) { return config.NewBlankConfig(), nil },
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
},
IO: ios,
SecretName: "cool_secret",
Body: "a secret",
RandomOverride: fakeRandom,
Application: tt.opts.Application,
HasRepoOverride: tt.opts.HasRepoOverride,
Remotes: tt.opts.Remotes,
}
err := setRun(opts)
if tt.wantErr {
assert.EqualError(t, err, tt.errMsg)
} else {
assert.NoError(t, err)
}
reg.Verify(t)
if tt.wantApp != "" && !tt.wantErr {
data, err := io.ReadAll(reg.Requests[1].Body)
assert.NoError(t, err)
var payload SecretPayload
err = json.Unmarshal(data, &payload)
assert.NoError(t, err)
assert.Equal(t, payload.KeyID, "123")
assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
}
})
}
}
func fakeRandom() io.Reader {
return bytes.NewReader(bytes.Repeat([]byte{5}, 32))
}

View file

@ -59,7 +59,7 @@ func MutuallyExclusive(message string, conditions ...bool) error {
}
func ValidateHasOnlyOneRemote(hasRepoOverride bool, remotes func() (ghContext.Remotes, error)) error {
if !hasRepoOverride {
if !hasRepoOverride && remotes != nil {
remotes, err := remotes()
if err != nil {
return err