add unit test

This commit is contained in:
ejahnGithub 2025-05-30 08:17:21 -07:00
parent ab49b2abbc
commit 71c2361dfc
11 changed files with 421 additions and 13 deletions

1
go.mod
View file

@ -58,6 +58,7 @@ require (
google.golang.org/protobuf v1.36.6
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.0.3
)
require (

6
go.sum
View file

@ -243,6 +243,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go=
github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
@ -410,6 +411,7 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -483,6 +485,7 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
@ -560,6 +563,7 @@ golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCR
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -597,11 +601,13 @@ golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc=
google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=

View file

@ -6,6 +6,13 @@ import (
"github.com/cli/cli/v2/pkg/cmd/attestation/test/data"
)
func makeTestReleaseAttestation() Attestation {
return Attestation{
Bundle: data.GitHubReleaseBundle(nil),
BundleURL: "https://example.com",
}
}
func makeTestAttestation() Attestation {
return Attestation{Bundle: data.SigstoreBundle(nil), BundleURL: "https://example.com"}
}
@ -26,8 +33,12 @@ func (m MockClient) GetTrustDomain() (string, error) {
func OnGetByDigestSuccess(params FetchParams) ([]*Attestation, error) {
att1 := makeTestAttestation()
att2 := makeTestAttestation()
att3 := makeTestReleaseAttestation()
attestations := []*Attestation{&att1, &att2}
if params.PredicateType != "" {
if params.PredicateType == "https://in-toto.io/attestation/release/v0.1" {
attestations = append(attestations, &att3)
}
return FilterAttestations(params.PredicateType, attestations)
}

View file

@ -0,0 +1,28 @@
a # frozen_string_literal: true
source "https://rubygems.org"
source "https://rubygems.pkg.github.com/github" do
gem "entitlements-aad-plugin", "~> 1.0"
gem "entitlements-app", "~> 1.2"
gem "entitlements-github-plugin", "~> 1.2"
gem "entitlements-gitrepo-auditor-plugin", "~> 1.0"
gem "entitlements-jit-github-plugin", "~> 1.0"
gem "entitlements-lib", "~> 0.2"
gem "entitlements-stafftools-plugin", "~> 1.0"
end
group :development do
gem "base64", "~> 0.2.0"
gem "irb", "~> 1.15"
gem "pry", "~> 0.14"
gem "pry-byebug", "~> 3.9"
gem "pry-rescue", "~> 1.6"
gem "rspec", "~> 3.13"
gem "rubocop", "~> 1.71"
gem "rubocop-github", "~> 0.20.0"
gem "rubocop-performance"
gem "rubocop-rspec", "~> 3.4.0"
gem "simplecov", "~> 0.21"
gem "simplecov-erb", "~> 1.0.0"
end

View file

@ -10,6 +10,9 @@ import (
//go:embed sigstore-js-2.1.0-bundle.json
var SigstoreBundleRaw []byte
//go:embed github_release_bundle.json
var GitHubReleaseBundleRaw []byte
// SigstoreBundle returns a test sigstore-go bundle.Bundle
func SigstoreBundle(t *testing.T) *bundle.Bundle {
b := &bundle.Bundle{}
@ -19,3 +22,12 @@ func SigstoreBundle(t *testing.T) *bundle.Bundle {
}
return b
}
func GitHubReleaseBundle(t *testing.T) *bundle.Bundle {
b := &bundle.Bundle{}
err := b.UnmarshalJSON(GitHubReleaseBundleRaw)
if err != nil {
t.Fatalf("failed to unmarshal GitHub release bundle: %v", err)
}
return b
}

View file

@ -0,0 +1,24 @@
{
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"verificationMaterial": {
"timestampVerificationData": {
"rfc3161Timestamps": [
{
"signedTimestamp": "MIIC0TADAgEAMIICyAYJKoZIhvcNAQcCoIICuTCCArUCAQMxDTALBglghkgBZQMEAgIwgbwGCyqGSIb3DQEJEAEEoIGsBIGpMIGmAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgGvFc6nUuLhnXfhM9p0DV91c5kHvafP1hs9BX8KYeeSYCFQDhjGrIIiaH/jkMdN6HUsErnUfrlRgPMjAyNTA1MTMyMzAzNTFaMAMCAQGgNqQ0MDIxFTATBgNVBAoTDEdpdEh1YiwgSW5jLjEZMBcGA1UEAxMQVFNBIFRpbWVzdGFtcGluZ6AAMYIB3jCCAdoCAQEwSjAyMRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xGTAXBgNVBAMTEFRTQSBpbnRlcm1lZGlhdGUCFB+7MIjE5/rL4XA4fNDnmXHA04+wMAsGCWCGSAFlAwQCAqCCAQUwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA1MTMyMzAzNTFaMD8GCSqGSIb3DQEJBDEyBDDVh2oDCJy7ustugLKfVcUSNjo5M2MFMNKIU11sIQDCNOo5gbj9R97sCWXNnfmUztMwgYcGCyqGSIb3DQEJEAIvMXgwdjB0MHIEIHuISsKSyiJtlhGjT+RyS+tYQ7iwCMsMCTGmz2NK3D7DME4wNqQ0MDIxFTATBgNVBAoTDEdpdEh1YiwgSW5jLjEZMBcGA1UEAxMQVFNBIGludGVybWVkaWF0ZQIUH7swiMTn+svhcDh80OeZccDTj7AwCgYIKoZIzj0EAwMEZzBlAjAqp/fYVfQcU9aMcmTIZvb0cxk00OaVBYLzuiIvcRqkMdAJiz/gSxOWU0AQjEPskHUCMQCrUKlZR4shPZuMvY6CCUOhxxKq/6LUoccWNHyL6sGkHRXE7j9HETh4uLKzRwNDVVA="
}
]
},
"certificate": {
"rawBytes": "MIICKjCCAbCgAwIBAgIUaa62dj98DUB+TpyvKtVaR4vGSM0wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MDMxMDE1MDMwMloXDTI2MDMxMDE1MDMwMlowKjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMREwDwYDVQQDEwhBdHRlc3RlcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIMB7plPnZvBRlC2lvAocKTAqAPMJqstEqYk26e9vDJDC1yqoiHxZfPV4W/1RqUMZD1dFKm9t4RiSmm73/QnQKajgaUwgaIwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOqaGpr5SbdYk5CQXsmmDZCBHR+XMB8GA1UdIwQYMBaAFMDhuFKkS08+3no4EQbPSY6hRZszMC0GA1UdEQQmMCSGImh0dHBzOi8vZG90Y29tLnJlbGVhc2VzLmdpdGh1Yi5jb20wCgYIKoZIzj0EAwMDaAAwZQIwWFdF6xcXazHVPHEAtd1SeaizLdY1erRl5hK+XlwhfpnasQHHZ9bdu4Zj8ARhW/AhAjEArujhmJGo7Fi4/Ek1RN8bufs6UhIQneQd/pxE8QdorwZkj2C8nf2EzrUYzlxKfktC"
}
},
"dsseEnvelope": {
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJ1cmkiOiJwa2c6Z2l0aHViL2JkZWhhbWVyL2RlbG1lQHY1IiwiZGlnZXN0Ijp7InNoYTEiOiJjNWUxN2E2MmUwNmExZDIwMTU3MDI0OWM2MWZhZTUzMWU5MjQ0ZTFiIn19LHsibmFtZSI6ImEuemlwIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImY3MTY1ODQ4ZjlmNWRkYzU3OGQ3YWRiZDFmNTY2YTM5NDE2OTM4NWM3M2JkODhiZjYwZGY3ZTc1OWRiOGUwOGQifX0seyJuYW1lIjoiYi56aXAiLCJkaWdlc3QiOnsic2hhMjU2IjoiOGI3ZWIxNTcyMzQ2NjkyZmZkM2FlMDEyNDhjNzBhMzQxYWUzYWE4YmUxZGY4YjEyMzQ2YjUwYWNiOTAwMjI4MiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2luLXRvdG8uaW8vYXR0ZXN0YXRpb24vcmVsZWFzZS92MC4xIiwicHJlZGljYXRlIjp7Im93bmVySWQiOiIzOTgwMjciLCJwdXJsIjoicGtnOmdpdGh1Yi9iZGVoYW1lci9kZWxtZUB2NSIsInJlbGVhc2VJZCI6IjIxODQxOTIxNyIsInJlcG9zaXRvcnkiOiJiZGVoYW1lci9kZWxtZSIsInJlcG9zaXRvcnlJZCI6IjkwNTk4ODA0NCIsInRhZyI6InY1In19",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{
"sig": "MEQCIH6LDUanQYOCPovZlIqI1cE49SiGJdexR65qsAZHohsZAiA9w3usgPWtgn5voB8bRvpJQtjEVqC5eMDh3mJEdyMcXw=="
}
]
}
}

View file

@ -281,3 +281,11 @@ func StubFetchRelease(t *testing.T, reg *httpmock.Registry, owner, repoName, tag
)
}
}
func StubFetchRefSHA(t *testing.T, reg *httpmock.Registry, owner, repoName, tagName, sha string) {
path := fmt.Sprintf("repos/%s/%s/git/refs/tags/%s", owner, repoName, tagName)
reg.Register(
httpmock.REST("GET", path),
httpmock.StringResponse(fmt.Sprintf(`{"object": {"sha": "%s"}}`, sha)),
)
}

View file

@ -27,14 +27,17 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions)
Use: "verify-asset <tag> <file-path>",
Short: "Verify that a given asset originated from a specific GitHub Release.",
Hidden: true,
Args: cobra.ExactArgs(2),
Args: cobra.MaximumNArgs(2),
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return cmdutil.FlagErrorf("You must specify a tag and a file path")
}
tagName := args[0]
assetFilePath := args[1]
if len(args) == 2 {
opts.TagName = args[0]
opts.AssetFilePath = args[1]
} else if len(args) == 1 {
opts.AssetFilePath = args[0]
} else {
return cmdutil.FlagErrorf("you must specify an asset filepath")
}
httpClient, err := f.HttpClient()
if err != nil {
@ -53,8 +56,8 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions)
}
*opts = attestation.AttestOptions{
TagName: tagName,
AssetFilePath: assetFilePath,
TagName: opts.TagName,
AssetFilePath: opts.AssetFilePath,
Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(),
APIClient: api.NewLiveClient(httpClient, hostname, logger),
Limit: 10,
@ -114,6 +117,15 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions)
func verifyAssetRun(opts *attestation.AttestOptions) error {
ctx := context.Background()
if opts.TagName == "" {
release, err := shared.FetchLatestRelease(ctx, opts.HttpClient, opts.BaseRepo)
if err != nil {
return err
}
opts.TagName = release.TagName
}
fileName := getFileName(opts.AssetFilePath)
// calculate the digest of the file

View file

@ -0,0 +1,158 @@
package verifyasset
import (
"bytes"
"net/http"
"testing"
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
"github.com/cli/cli/v2/pkg/cmd/release/attestation"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/release/shared"
"github.com/cli/cli/v2/pkg/httpmock"
)
func TestNewCmdVerifyAsset_Args(t *testing.T) {
tests := []struct {
name string
args []string
wantTag string
wantFile string
wantErr string
}{
{
name: "valid args",
args: []string{"v1.2.3", "../../attestation/test/data/a.zip"},
wantTag: "v1.2.3",
wantFile: "../../attestation/test/data/a.zip",
},
{
name: "valid flag with no tag",
args: []string{"../../attestation/test/data/a.zip"},
wantFile: "../../attestation/test/data/a.zip",
},
{
name: "no args",
args: []string{},
wantErr: "you must specify an asset filepath",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testIO, _, _, _ := iostreams.Test()
var testReg httpmock.Registry
var metaResp = api.MetaResponse{
Domains: api.Domain{
ArtifactAttestations: api.ArtifactAttestations{},
},
}
testReg.Register(httpmock.REST(http.MethodGet, "meta"),
httpmock.StatusJSONResponse(200, &metaResp))
f := &cmdutil.Factory{
IOStreams: testIO,
HttpClient: func() (*http.Client, error) {
reg := &testReg
client := &http.Client{}
httpmock.ReplaceTripper(client, reg)
return client, nil
},
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
},
}
var opts *attestation.AttestOptions
cmd := NewCmdVerifyAsset(f, func(o *attestation.AttestOptions) error {
opts = o
return nil
})
cmd.SetArgs(tt.args)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
_, err := cmd.ExecuteC()
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantTag, opts.TagName)
assert.Equal(t, tt.wantFile, opts.AssetFilePath)
}
})
}
}
func Test_verifyAssetRun_Success(t *testing.T) {
ios, _, _, _ := iostreams.Test()
tagName := "v1.2.3"
fakeHTTP := &httpmock.Registry{}
defer fakeHTTP.Verify(t)
fakeSHA := "1234567890abcdef1234567890abcdef12345678"
shared.StubFetchRefSHA(t, fakeHTTP, "owner", "repo", tagName, fakeSHA)
baseRepo, err := ghrepo.FromFullName("owner/repo")
require.NoError(t, err)
opts := &attestation.AttestOptions{
TagName: tagName,
AssetFilePath: "../../attestation/test/data/a.zip",
Repo: "owner/repo",
Owner: "owner",
Limit: 10,
Logger: io.NewHandler(ios),
APIClient: api.NewTestClient(),
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
HttpClient: &http.Client{Transport: fakeHTTP},
BaseRepo: baseRepo,
}
err = verifyAssetRun(opts)
require.NoError(t, err)
}
func Test_verifyAssetRun_NoAttestation(t *testing.T) {
ios, _, _, _ := iostreams.Test()
opts := &attestation.AttestOptions{
TagName: "v1.2.3",
AssetFilePath: "artifact.tgz",
Repo: "owner/repo",
Limit: 10,
Logger: io.NewHandler(ios),
IO: ios,
APIClient: api.NewTestClient(),
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
EC: verification.EnforcementCriteria{},
}
err := verifyAssetRun(opts)
require.Error(t, err, "failed to get open local artifact: open artifact.tgz: no such file or director")
}
func Test_getFileName(t *testing.T) {
tests := []struct {
input string
want string
}{
{"foo/bar/baz.txt", "baz.txt"},
{"baz.txt", "baz.txt"},
{"/tmp/foo.tar.gz", "foo.tar.gz"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := getFileName(tt.input)
assert.Equal(t, tt.want, got)
})
}
}

View file

@ -28,14 +28,12 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro
Use: "verify [<tag>]",
Short: "Verify the attestation for a GitHub Release.",
Hidden: true,
Args: cobra.ExactArgs(1),
Args: cobra.MaximumNArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return cmdutil.FlagErrorf("You must specify a tag")
if len(args) > 0 {
opts.TagName = args[0]
}
opts.TagName = args[0]
httpClient, err := f.HttpClient()
if err != nil {
return err
@ -115,6 +113,14 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro
func verifyRun(opts *attestation.AttestOptions) error {
ctx := context.Background()
if opts.TagName == "" {
release, err := shared.FetchLatestRelease(ctx, opts.HttpClient, opts.BaseRepo)
if err != nil {
return err
}
opts.TagName = release.TagName
}
ref, err := shared.FetchRefSHA(ctx, opts.HttpClient, opts.BaseRepo, opts.TagName)
if err != nil {
return err

View file

@ -0,0 +1,142 @@
package verify
import (
"bytes"
"net/http"
"testing"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
"github.com/cli/cli/v2/pkg/cmd/release/attestation"
"github.com/cli/cli/v2/pkg/cmd/release/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"
)
func TestNewCmdVerify_Args(t *testing.T) {
tests := []struct {
name string
args []string
wantTag string
wantErr string
}{
{
name: "valid tag arg",
args: []string{"v1.2.3"},
wantTag: "v1.2.3",
},
{
name: "no tag arg",
args: []string{},
wantTag: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testIO, _, _, _ := iostreams.Test()
var testReg httpmock.Registry
var metaResp = api.MetaResponse{
Domains: api.Domain{
ArtifactAttestations: api.ArtifactAttestations{},
},
}
testReg.Register(httpmock.REST(http.MethodGet, "meta"),
httpmock.StatusJSONResponse(200, &metaResp))
f := &cmdutil.Factory{
IOStreams: testIO,
HttpClient: func() (*http.Client, error) {
reg := &testReg
client := &http.Client{}
httpmock.ReplaceTripper(client, reg)
return client, nil
},
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
},
}
var opts *attestation.AttestOptions
cmd := NewCmdVerify(f, func(o *attestation.AttestOptions) error {
opts = o
return nil
})
cmd.SetArgs(tt.args)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
_, err := cmd.ExecuteC()
require.NoError(t, err)
assert.Equal(t, tt.wantTag, opts.TagName)
})
}
}
func Test_verifyRun_Success(t *testing.T) {
ios, _, _, _ := iostreams.Test()
tagName := "v1.2.3"
fakeHTTP := &httpmock.Registry{}
defer fakeHTTP.Verify(t)
fakeSHA := "1234567890abcdef1234567890abcdef12345678"
shared.StubFetchRefSHA(t, fakeHTTP, "owner", "repo", tagName, fakeSHA)
baseRepo, err := ghrepo.FromFullName("owner/repo")
require.NoError(t, err)
opts := &attestation.AttestOptions{
TagName: tagName,
Repo: "owner/repo",
Owner: "owner",
Limit: 10,
Logger: io.NewHandler(ios),
APIClient: api.NewTestClient(),
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
HttpClient: &http.Client{Transport: fakeHTTP},
BaseRepo: baseRepo,
}
ec, err := attestation.NewEnforcementCriteria(opts)
require.NoError(t, err)
opts.EC = ec
err = verifyRun(opts)
require.NoError(t, err)
}
func Test_verifyRun_NoAttestation(t *testing.T) {
ios, _, _, _ := iostreams.Test()
tagName := "v1.2.3"
fakeHTTP := &httpmock.Registry{}
defer fakeHTTP.Verify(t)
fakeSHA := "1234567890abcdef1234567890abcdef12345678"
shared.StubFetchRefSHA(t, fakeHTTP, "owner", "repo", tagName, fakeSHA)
baseRepo, err := ghrepo.FromFullName("owner/repo")
require.NoError(t, err)
opts := &attestation.AttestOptions{
TagName: tagName,
Repo: "owner/repo",
Owner: "owner",
Limit: 10,
Logger: io.NewHandler(ios),
APIClient: api.NewFailTestClient(),
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
HttpClient: &http.Client{Transport: fakeHTTP},
BaseRepo: baseRepo,
}
ec, err := attestation.NewEnforcementCriteria(opts)
require.NoError(t, err)
opts.EC = ec
err = verifyRun(opts)
require.Error(t, err, "failed to fetch attestations from owner/repo")
}