add tests for CLI commands
Signed-off-by: Meredith Lancaster <malancas@github.com>
This commit is contained in:
parent
be8ce2b23a
commit
9becdc5a26
13 changed files with 519 additions and 232 deletions
|
|
@ -29,9 +29,9 @@ func NewCmdAttestation(f *cmdutil.Factory) *cobra.Command {
|
|||
`, "`"),
|
||||
}
|
||||
|
||||
root.AddCommand(download.NewDownloadCmd(f))
|
||||
root.AddCommand(inspect.NewInspectCmd(f))
|
||||
root.AddCommand(verify.NewVerifyCmd(f))
|
||||
root.AddCommand(download.NewDownloadCmd(f, nil))
|
||||
root.AddCommand(inspect.NewInspectCmd(f, nil))
|
||||
root.AddCommand(verify.NewVerifyCmd(f, nil))
|
||||
root.AddCommand(tufrootverify.NewTUFRootVerifyCmd(f))
|
||||
|
||||
return root
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDownloadCmd(f *cmdutil.Factory) *cobra.Command {
|
||||
func NewDownloadCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command {
|
||||
opts := &Options{}
|
||||
downloadCmd := &cobra.Command{
|
||||
Use: "download [<file path> | oci://<OCI image URI>]",
|
||||
|
|
@ -85,6 +85,11 @@ func NewDownloadCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
if err := auth.IsHostSupported(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
if err := runDownload(opts); err != nil {
|
||||
return fmt.Errorf("Failed to download the artifact's bundle(s): %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package download
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
|
@ -11,10 +13,164 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewDownloadCmd(t *testing.T) {
|
||||
testIO, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: testIO,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
reg := &httpmock.Registry{}
|
||||
client := &http.Client{}
|
||||
httpmock.ReplaceTripper(client, reg)
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
tempDir := t.TempDir()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants Options
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid digest-alg flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --digest-alg sha384",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha384",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Missing digest-alg flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing owner and repo flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Has both owner and repo flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --repo sigstore/sigstore-js",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Repo: "sigstore/sigstore-js",
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Uses default limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Uses custom limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --limit 101",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 101,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Uses invalid limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --limit 0",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
APIClient: api.NewTestClient(),
|
||||
OCIClient: oci.MockClient{},
|
||||
DigestAlgorithm: "sha256",
|
||||
Owner: "sigstore",
|
||||
OutputPath: tempDir,
|
||||
Limit: 0,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var opts *Options
|
||||
cmd := NewDownloadCmd(f, func(o *Options) error {
|
||||
opts = o
|
||||
return nil
|
||||
})
|
||||
|
||||
argv, err := shlex.Split(tc.cli)
|
||||
assert.NoError(t, err)
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
_, err = cmd.ExecuteC()
|
||||
if tc.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm)
|
||||
assert.Equal(t, tc.wants.Limit, opts.Limit)
|
||||
assert.Equal(t, tc.wants.Owner, opts.Owner)
|
||||
assert.Equal(t, tc.wants.Repo, opts.Repo)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunDownload(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
minLimit = 1
|
||||
maxLimit = 1000
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
APIClient api.Client
|
||||
ArtifactPath string
|
||||
|
|
@ -23,22 +27,9 @@ type Options struct {
|
|||
}
|
||||
|
||||
func (opts *Options) AreFlagsValid() error {
|
||||
if opts.Owner == "" {
|
||||
return fmt.Errorf("owner must be provided")
|
||||
}
|
||||
|
||||
// DigestAlgorithm must not be empty
|
||||
if opts.DigestAlgorithm == "" {
|
||||
return fmt.Errorf("digest-alg cannot be empty")
|
||||
}
|
||||
|
||||
if !digest.IsValidDigestAlgorithm(opts.DigestAlgorithm) {
|
||||
return fmt.Errorf("invalid digest algorithm '%s' provided in digest-alg", opts.DigestAlgorithm)
|
||||
}
|
||||
|
||||
// Check that limit is between 1 and 1000
|
||||
if opts.Limit < 1 || opts.Limit > 1000 {
|
||||
return fmt.Errorf("limit %d not allowed, must be between 1 and 1000", opts.Limit)
|
||||
if opts.Limit < minLimit || opts.Limit > maxLimit {
|
||||
return fmt.Errorf("limit %d not allowed, must be between %d and %d", opts.Limit, minLimit, maxLimit)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,53 +1,34 @@
|
|||
package download
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAreFlagsValid(t *testing.T) {
|
||||
t.Run("missing Owner", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
limit int
|
||||
}{
|
||||
{
|
||||
name: "Limit is too low",
|
||||
limit: 0,
|
||||
},
|
||||
{
|
||||
name: "Limit is too high",
|
||||
limit: 1001,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
opts := Options{
|
||||
DigestAlgorithm: "sha512",
|
||||
Limit: tc.limit,
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "owner must be provided")
|
||||
})
|
||||
|
||||
t.Run("missing DigestAlgorithm", func(t *testing.T) {
|
||||
opts := Options{
|
||||
Owner: "github",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "digest-alg cannot be empty")
|
||||
})
|
||||
|
||||
t.Run("Limit is too low", func(t *testing.T) {
|
||||
opts := Options{
|
||||
DigestAlgorithm: "sha512",
|
||||
Limit: 0,
|
||||
Owner: "github",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "limit 0 not allowed, must be between 1 and 1000")
|
||||
})
|
||||
|
||||
t.Run("Limit is too high", func(t *testing.T) {
|
||||
opts := Options{
|
||||
DigestAlgorithm: "sha512",
|
||||
Limit: 1001,
|
||||
Owner: "github",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "limit 1001 not allowed, must be between 1 and 1000")
|
||||
})
|
||||
expectedErrMsg := fmt.Sprintf("limit %d not allowed, must be between 1 and 1000", tc.limit)
|
||||
require.ErrorContains(t, err, expectedErrMsg)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewInspectCmd(f *cmdutil.Factory) *cobra.Command {
|
||||
func NewInspectCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command {
|
||||
opts := &Options{}
|
||||
inspectCmd := &cobra.Command{
|
||||
Use: "inspect [<file path> | oci://<OCI image URI>] --bundle <path-to-bundle>",
|
||||
|
|
@ -56,11 +56,6 @@ func NewInspectCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
// set the artifact path
|
||||
opts.ArtifactPath = args[0]
|
||||
|
||||
// Check that the given flag combination is valid
|
||||
if err := opts.AreFlagsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean file path options
|
||||
opts.Clean()
|
||||
|
||||
|
|
@ -72,6 +67,11 @@ func NewInspectCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
if err := auth.IsHostSupported(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
if err := runInspect(opts); err != nil {
|
||||
return fmt.Errorf("Failed to inspect the artifact and bundle: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
package inspect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -20,6 +28,113 @@ var (
|
|||
bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json")
|
||||
)
|
||||
|
||||
func TestNewInspectCmd(t *testing.T) {
|
||||
testIO, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: testIO,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
reg := &httpmock.Registry{}
|
||||
client := &http.Client{}
|
||||
httpmock.ReplaceTripper(client, reg)
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants Options
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid digest-alg flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --digest-alg sha384",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha384",
|
||||
OCIClient: oci.MockClient{},
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Use default digest-alg value",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha256",
|
||||
OCIClient: oci.MockClient{},
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Use custom digest-alg value",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --digest-alg sha512",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha512",
|
||||
OCIClient: oci.MockClient{},
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing bundle flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
OCIClient: oci.MockClient{},
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Has both verbose and quiet flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --verbose --quiet",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha256",
|
||||
OCIClient: oci.MockClient{},
|
||||
Verbose: true,
|
||||
Quiet: true,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var opts *Options
|
||||
cmd := NewInspectCmd(f, func(o *Options) error {
|
||||
opts = o
|
||||
return nil
|
||||
})
|
||||
|
||||
argv, err := shlex.Split(tc.cli)
|
||||
assert.NoError(t, err)
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
_, err = cmd.ExecuteC()
|
||||
if tc.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath)
|
||||
assert.Equal(t, tc.wants.BundlePath, opts.BundlePath)
|
||||
assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm)
|
||||
assert.Equal(t, tc.wants.JsonResult, opts.JsonResult)
|
||||
assert.Equal(t, tc.wants.Verbose, opts.Verbose)
|
||||
assert.Equal(t, tc.wants.Quiet, opts.Quiet)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInspect(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: artifactPath,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package inspect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
)
|
||||
|
|
@ -25,23 +23,3 @@ type Options struct {
|
|||
func (opts *Options) Clean() {
|
||||
opts.BundlePath = filepath.Clean(opts.BundlePath)
|
||||
}
|
||||
|
||||
// AreFlagsValid checks that the provided flag combination is valid
|
||||
// and returns an error otherwise
|
||||
func (opts *Options) AreFlagsValid() error {
|
||||
// either BundlePath or Repo must be set to configure offline or online mode
|
||||
if opts.BundlePath == "" {
|
||||
return fmt.Errorf("bundle must be provided")
|
||||
}
|
||||
|
||||
// DigestAlgorithm must not be empty
|
||||
if opts.DigestAlgorithm == "" {
|
||||
return fmt.Errorf("digest-alg cannot be empty")
|
||||
}
|
||||
|
||||
if !digest.IsValidDigestAlgorithm(opts.DigestAlgorithm) {
|
||||
return fmt.Errorf("invalid digest algorithm '%s' provided in digest-alg", opts.DigestAlgorithm)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
package inspect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAreFlagsValid(t *testing.T) {
|
||||
artifactPath := test.NormalizeRelativePath("../test/data/public-good/sigstore-js-2.1.0.tgz")
|
||||
bundlePath := test.NormalizeRelativePath("../test/data/public-good/sigstore-js-2.1.0-bundle.json")
|
||||
|
||||
t.Run("missing BundlePath", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: artifactPath,
|
||||
DigestAlgorithm: "sha512",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "bundle must be provided")
|
||||
})
|
||||
|
||||
t.Run("missing DigestAlgorithm", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: artifactPath,
|
||||
BundlePath: bundlePath,
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "digest-alg cannot be empty")
|
||||
})
|
||||
|
||||
t.Run("invalid DigestAlgorithm", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: artifactPath,
|
||||
BundlePath: bundlePath,
|
||||
DigestAlgorithm: "sha1",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid digest algorithm 'sha1' provided in digest-alg")
|
||||
})
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
)
|
||||
|
|
@ -63,35 +62,6 @@ func (opts *Options) SetPolicyFlags() {
|
|||
// AreFlagsValid checks that the provided flag combination is valid
|
||||
// and returns an error otherwise
|
||||
func (opts *Options) AreFlagsValid() error {
|
||||
// either BundlePath or Repo must be set to configure offline or online mode
|
||||
if opts.BundlePath == "" && opts.Repo == "" && opts.Owner == "" {
|
||||
return fmt.Errorf("either bundle or repo or owner must be provided")
|
||||
}
|
||||
|
||||
// DigestAlgorithm must not be empty
|
||||
if opts.DigestAlgorithm == "" {
|
||||
return fmt.Errorf("digest-alg cannot be empty")
|
||||
}
|
||||
|
||||
if !digest.IsValidDigestAlgorithm(opts.DigestAlgorithm) {
|
||||
return fmt.Errorf("invalid digtest algorithm '%s' provided in digest-alg", opts.DigestAlgorithm)
|
||||
}
|
||||
|
||||
// OIDCIssuer must not be empty
|
||||
if opts.OIDCIssuer == "" {
|
||||
return fmt.Errorf("cert-oidc-issuer cannot be empty")
|
||||
}
|
||||
|
||||
// either Owner or Repo must be supplied
|
||||
if opts.Owner == "" && opts.Repo == "" {
|
||||
return fmt.Errorf("owner or repo must be provided")
|
||||
}
|
||||
|
||||
// SAN or SAN regex are mutually exclusive, only one can be provided
|
||||
if opts.SAN != "" && opts.SANRegex != "" {
|
||||
return fmt.Errorf("cert-identity and cert-identity-regex cannot both be provided")
|
||||
}
|
||||
|
||||
// check that Repo is in the expected format if provided
|
||||
if opts.Repo != "" {
|
||||
// we expect the repo argument to be in the format <OWNER>/<REPO>
|
||||
|
|
|
|||
|
|
@ -14,60 +14,6 @@ var (
|
|||
)
|
||||
|
||||
func TestAreFlagsValid(t *testing.T) {
|
||||
t.Run("missing BundlePath, Repo, and Owner", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
DigestAlgorithm: "sha512",
|
||||
OIDCIssuer: "some issuer",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "either bundle or repo or owner must be provided")
|
||||
})
|
||||
|
||||
t.Run("missing DigestAlgorithm", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
OIDCIssuer: "some issuer",
|
||||
Owner: "sigstore",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "digest-alg cannot be empty")
|
||||
})
|
||||
|
||||
t.Run("missing Owner and Repo", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
DigestAlgorithm: "sha512",
|
||||
OIDCIssuer: "some issuer",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "owner or repo must be provided")
|
||||
})
|
||||
|
||||
t.Run("has both SAN and SANRegex", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
DigestAlgorithm: "sha512",
|
||||
OIDCIssuer: "some issuer",
|
||||
Owner: "sigstore",
|
||||
SAN: "some san",
|
||||
SANRegex: "^some san regex$",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "cert-identity and cert-identity-regex cannot both be provided")
|
||||
})
|
||||
|
||||
t.Run("has invalid Repo value", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
|
|
@ -81,19 +27,6 @@ func TestAreFlagsValid(t *testing.T) {
|
|||
require.ErrorContains(t, err, "invalid value provided for repo")
|
||||
})
|
||||
|
||||
t.Run("missing OIDCIssuer", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
DigestAlgorithm: "sha512",
|
||||
Owner: "sigstore",
|
||||
}
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "cert-oidc-issuer cannot be empty")
|
||||
})
|
||||
|
||||
t.Run("invalid limit < 0", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
var ErrNoMatchingSLSAPredicate = fmt.Errorf("the attestation does not have the expected SLSA predicate type: %s", SLSAPredicateType)
|
||||
|
||||
func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command {
|
||||
func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command {
|
||||
opts := &Options{}
|
||||
verifyCmd := &cobra.Command{
|
||||
Use: "verify <artifact-path-or-url>",
|
||||
|
|
@ -100,6 +100,11 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
if err := auth.IsHostSupported(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
if err := runVerify(opts); err != nil {
|
||||
return fmt.Errorf("Failed to verify the artifact: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package verify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
|
|
@ -8,6 +10,12 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/cmd/attestation/logging"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/sigstore/sigstore-go/pkg/verify"
|
||||
|
|
@ -20,6 +28,199 @@ const (
|
|||
SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/"
|
||||
)
|
||||
|
||||
func TestNewVerifyCmd(t *testing.T) {
|
||||
testIO, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: testIO,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
reg := &httpmock.Registry{}
|
||||
client := &http.Client{}
|
||||
httpmock.ReplaceTripper(client, reg)
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants Options
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid digest-alg flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --digest-alg sha384 --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha384",
|
||||
Limit: 30,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Use default digest-alg value",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha256",
|
||||
Limit: 30,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Use custom digest-alg value",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --digest-alg sha512 --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha512",
|
||||
Limit: 30,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing owner and repo flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
Limit: 30,
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Has both owner and repo flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --repo sigstore/sigstore-js",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
Repo: "sigstore/sigstore-js",
|
||||
Limit: 30,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Uses default limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
Limit: 30,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Uses custom limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --limit 101",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
Limit: 101,
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "Uses invalid limit flag",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --limit 0",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
Limit: 0,
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Has both verbose and quiet flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --bundle ../test/data/sigstore-js-2.1.0-bundle.json --owner sigstore --verbose --quiet",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
DigestAlgorithm: "sha256",
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Verbose: true,
|
||||
Quiet: true,
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "Has both cert-identity and cert-identity-regex flags",
|
||||
cli: "../test/data/sigstore-js-2.1.0.tgz --owner sigstore --cert-identity https://github.com/sigstore/ --cert-identity-regex ^https://github.com/sigstore/",
|
||||
wants: Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
DigestAlgorithm: "sha256",
|
||||
Limit: 30,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SAN: "https://github.com/sigstore/",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
},
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var opts *Options
|
||||
cmd := NewVerifyCmd(f, func(o *Options) error {
|
||||
opts = o
|
||||
return nil
|
||||
})
|
||||
|
||||
argv, err := shlex.Split(tc.cli)
|
||||
assert.NoError(t, err)
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
_, err = cmd.ExecuteC()
|
||||
if tc.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath)
|
||||
assert.Equal(t, tc.wants.BundlePath, opts.BundlePath)
|
||||
assert.Equal(t, tc.wants.CustomTrustedRoot, opts.CustomTrustedRoot)
|
||||
assert.Equal(t, tc.wants.DenySelfHostedRunner, opts.DenySelfHostedRunner)
|
||||
assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm)
|
||||
assert.Equal(t, tc.wants.JsonResult, opts.JsonResult)
|
||||
assert.Equal(t, tc.wants.Limit, opts.Limit)
|
||||
assert.Equal(t, tc.wants.NoPublicGood, opts.NoPublicGood)
|
||||
assert.Equal(t, tc.wants.OIDCIssuer, opts.OIDCIssuer)
|
||||
assert.Equal(t, tc.wants.Owner, opts.Owner)
|
||||
assert.Equal(t, tc.wants.Quiet, opts.Quiet)
|
||||
assert.Equal(t, tc.wants.Repo, opts.Repo)
|
||||
assert.Equal(t, tc.wants.SAN, opts.SAN)
|
||||
assert.Equal(t, tc.wants.SANRegex, opts.SANRegex)
|
||||
assert.Equal(t, tc.wants.Verbose, opts.Verbose)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVerify(t *testing.T) {
|
||||
logger := logging.NewTestLogger()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue