Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
299d3af
feat(cli): improve evaluation reasoning output using existing fields
sachin9058 Apr 24, 2026
46ea753
refactor(cli): simplify evaluation details rendering and apply review…
sachin9058 Apr 25, 2026
e2fba50
chore: trigger CI rerun
sachin9058 Apr 25, 2026
156b8f7
refactor(cli): address review feedback and simplify details rendering
sachin9058 Apr 25, 2026
1268cee
test(cli): add golden tests for artifact get and enable test-friendly…
sachin9058 Apr 30, 2026
662d3fa
feat(cli): add artifact get command tests and align with GetCLIClient…
sachin9058 Apr 30, 2026
b806f75
test(cli): standardize CLI tests and improve validation
sachin9058 Apr 25, 2026
50167d8
Standardize artifact and profile CLI tests
sachin9058 Apr 25, 2026
1854d92
test(cli): align artifact tests with CmdTestCase + golden pattern
sachin9058 Apr 28, 2026
fbcda82
fix(cli): avoid GRPC wrapper in artifact list to prevent login flow i…
sachin9058 Apr 28, 2026
fb333b5
test(cli): add golden tests for artifact get and fix CI login issue
sachin9058 Apr 30, 2026
d85ea5d
test(cli): add artifact get tests, from filter case, and align test s…
sachin9058 Apr 30, 2026
0d81ca5
test(cli): refresh artifact get golden tests
sachin9058 Apr 30, 2026
00599e6
fix(cli): format generated artifact mock
sachin9058 Apr 30, 2026
2728e07
chore: trigger CI
sachin9058 Apr 30, 2026
f6cc27a
test(cli): remove JSON golden tests due to protojson instability (see…
sachin9058 May 1, 2026
231a024
test(cli): update goldens after rebase and renderer changes
sachin9058 May 1, 2026
f503cb3
refactor(cli): inline artifact list logic into RunE
sachin9058 May 1, 2026
8c4ab04
test(cli): remove remaining JSON goldens and finalize CLI test cleanup
sachin9058 May 1, 2026
0fba74f
refactor(cli): align with cli.GetCLIClient and remove custom RPC inje…
sachin9058 May 1, 2026
4556e51
chore(test): restore upstream comments in artifact_get_test.go
sachin9058 May 1, 2026
9aa2a52
chore: retrigger CI
sachin9058 May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 60 additions & 55 deletions cmd/cli/app/artifact/artifact_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
package artifact

import (
"context"
"fmt"
"strings"
"time"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"

"github.com/mindersec/minder/cmd/cli/app"
"github.com/mindersec/minder/internal/util"
Expand All @@ -25,69 +23,76 @@ var listCmd = &cobra.Command{
Use: "list",
Short: "List artifacts from a provider",
Long: `The artifact list subcommand will list artifacts from a provider.`,
RunE: cli.GRPCClientWrapRunE(listCommand),
}
PreRunE: func(cmd *cobra.Command, _ []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return fmt.Errorf("error binding flags: %w", err)
}

// listCommand is the artifact list subcommand
func listCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error {
client := minderv1.NewArtifactServiceClient(conn)
format := viper.GetString("output")
if !app.IsOutputFormatSupported(format) {
return cli.MessageAndError(fmt.Sprintf("Output format %s not supported", format), fmt.Errorf("invalid argument"))
}

provider := viper.GetString("provider")
project := viper.GetString("project")
format := viper.GetString("output")
fromFilter := viper.GetString("from")
return nil
},
RunE: func(cmd *cobra.Command, _ []string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting this from a function named listCommand to an anonymous function adjusts the indentation in a way that makes the GitHub diff very hard to review. Please put this back to listCommand, in the same style as every other command.

ctx := cmd.Context()

// Ensure the output format is supported
if !app.IsOutputFormatSupported(format) {
return cli.MessageAndError(fmt.Sprintf("Output format %s not supported", format), fmt.Errorf("invalid argument"))
}
client, closer, err := cli.GetCLIClient(cmd, minderv1.NewArtifactServiceClient)
if err != nil {
return err
}
defer closer()

// No longer print usage on returned error, since we've parsed our inputs
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true
provider := viper.GetString("provider")
project := viper.GetString("project")
format := viper.GetString("output")
fromFilter := viper.GetString("from")

artifactList, err := client.ListArtifacts(ctx, &minderv1.ListArtifactsRequest{
Context: &minderv1.Context{Provider: &provider, Project: &project},
From: fromFilter,
},
)

if err != nil {
return cli.MessageAndError("Couldn't list artifacts", err)
}

switch format {
case app.Table:
t := table.New(table.Simple, layouts.Default, cmd.OutOrStdout(),
[]string{"ID", "Type", "Owner", "Name", "Repository", "Visibility", "Creation date"})
for _, artifact := range artifactList.Results {
t.AddRow(
artifact.ArtifactPk,
artifact.Type,
artifact.GetOwner(),
artifact.GetName(),
artifact.Repository,
artifact.Visibility,
artifact.CreatedAt.AsTime().Format(time.RFC3339),
)
cmd.SilenceUsage = true

}
t.Render()
case app.JSON:
out, err := util.GetJsonFromProto(artifactList)
artifactList, err := client.ListArtifacts(ctx, &minderv1.ListArtifactsRequest{
Context: &minderv1.Context{Provider: &provider, Project: &project},
From: fromFilter,
})
if err != nil {
return cli.MessageAndError("Error getting json from proto", err)
return cli.MessageAndError("Couldn't list artifacts", err)
}
cmd.Println(out)
case app.YAML:
out, err := util.GetYamlFromProto(artifactList)
if err != nil {
return cli.MessageAndError("Error getting yaml from proto", err)

switch format {
case app.Table:
t := table.New(table.Simple, layouts.Default, cmd.OutOrStdout(),
[]string{"ID", "Type", "Owner", "Name", "Repository", "Visibility", "Creation date"})
for _, artifact := range artifactList.Results {
t.AddRow(
artifact.ArtifactPk,
artifact.Type,
artifact.GetOwner(),
artifact.GetName(),
artifact.Repository,
artifact.Visibility,
artifact.CreatedAt.AsTime().Format(time.RFC3339),
)
}
t.Render()

case app.JSON:
out, err := util.GetJsonFromProto(artifactList)
if err != nil {
return cli.MessageAndError("Error getting json from proto", err)
}
cmd.Println(out)

case app.YAML:
out, err := util.GetYamlFromProto(artifactList)
if err != nil {
return cli.MessageAndError("Error getting yaml from proto", err)
}
cmd.Println(out)
}
cmd.Println(out)
}

return nil
return nil
},
}

func init() {
Expand Down
73 changes: 73 additions & 0 deletions cmd/cli/app/artifact/artifact_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package artifact

import (
"context"
"testing"

"go.uber.org/mock/gomock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/mindersec/minder/internal/util/cli"
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
mockv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1/mock"
)

//nolint:paralleltest // Cannot run in parallel because it swaps global Viper/Stdout state
func TestArtifactListCommand(t *testing.T) {
setupSuccess := func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockArtifactServiceClient(ctrl)

mockResp := &minderv1.ListArtifactsResponse{}
cli.LoadFixture(t, "mock_artifact_list.json", mockResp)

client.EXPECT().
ListArtifacts(gomock.Any(), gomock.Any()).
Return(mockResp, nil).
Times(1)

return cli.WithRPCClient[minderv1.ArtifactServiceClient](context.Background(), client)
}

tests := []cli.CmdTestCase{
{
Name: "list artifacts - table output",
Args: []string{"artifact", "list", "-o", "table"},
MockSetup: setupSuccess,
GoldenFileName: "artifact_list.table",
},
{
Name: "list artifacts - yaml output",
Args: []string{"artifact", "list", "-o", "yaml"},
MockSetup: setupSuccess,
GoldenFileName: "artifact_list.yaml",
},
{
Name: "list artifacts with from filter",
Args: []string{"artifact", "list", "--from", "repository=org/repo", "-o", "table"},
MockSetup: setupSuccess,
GoldenFileName: "artifact_list_from.table",
},
{
Name: "server error handling",
Args: []string{"artifact", "list"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockArtifactServiceClient(ctrl)
client.EXPECT().
ListArtifacts(gomock.Any(), gomock.Any()).
Return(nil, status.Error(codes.Internal, "internal server error")).
Times(1)

return cli.WithRPCClient[minderv1.ArtifactServiceClient](context.Background(), client)
},
ExpectedError: "internal server error",
},
}

cli.RunCmdTests(t, tests, ArtifactCmd)
}
13 changes: 13 additions & 0 deletions cmd/cli/app/artifact/fixture/mock_artifact_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"results": [
{
"artifactPk": "111",
"name": "artifact-1",
"type": "image",
"owner": "owner-1",
"repository": "org/repo",
"visibility": "public",
"createdAt": "2024-01-02T15:04:05Z"
}
]
}
3 changes: 3 additions & 0 deletions cmd/cli/app/artifact/testdata/artifact_list.table.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ID │ TYPE │ OWNER │ NAME │ REPOSITORY │ VISIBILITY │ CREATION DATE
───────┼────────┼──────────┼──────────────┼──────────────┼──────────────┼───────────────────────────
111 │ image │ owner-1 │ artifact-1 │ org/repo │ public │ 2024-01-02T15:04:05Z
9 changes: 9 additions & 0 deletions cmd/cli/app/artifact/testdata/artifact_list.yaml.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
results:
- artifactPk: "111"
createdAt: "2024-01-02T15:04:05Z"
name: artifact-1
owner: owner-1
repository: org/repo
type: image
visibility: public

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ID │ TYPE │ OWNER │ NAME │ REPOSITORY │ VISIBILITY │ CREATION DATE
───────┼────────┼──────────┼──────────────┼──────────────┼──────────────┼───────────────────────────
111 │ image │ owner-1 │ artifact-1 │ org/repo │ public │ 2024-01-02T15:04:05Z
18 changes: 0 additions & 18 deletions cmd/cli/app/profile/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (

//nolint:paralleltest // Cannot run in parallel because it swaps global Viper/Stdout state
func TestGetCommand(t *testing.T) {
testID := "00000000-0000-0000-0000-000000000001"
testName := "test-profile"

tests := []cli.CmdTestCase{
Expand All @@ -39,23 +38,6 @@ func TestGetCommand(t *testing.T) {
},
GoldenFileName: "get_by_name_table.txt",
},
{
Name: "get by id json success",
Args: []string{"profile", "get", "--id", testID, "-o", "json"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProfileServiceClient(ctrl)
mockProf := &minderv1.Profile{}
cli.LoadFixture(t, "mock_profile_get.json", mockProf)

client.EXPECT().
GetProfileById(gomock.Any(), gomock.Any()).
Return(&minderv1.GetProfileByIdResponse{Profile: mockProf}, nil)

return cli.WithRPCClient[minderv1.ProfileServiceClient](context.Background(), client)
},
GoldenFileName: "get_by_id.json",
},
{
Name: "failure missing id and name",
Args: []string{"profile", "get"},
Expand Down
18 changes: 0 additions & 18 deletions cmd/cli/app/profile/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,6 @@ func TestListCommand(t *testing.T) {
},
GoldenFileName: "list_profiles_table.txt",
},
{
Name: "list profiles json success",
Args: []string{"profile", "list", "-o", "json"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProfileServiceClient(ctrl)

mockResp := &minderv1.ListProfilesResponse{}
cli.LoadFixture(t, "mock_profile_list.json", mockResp)

client.EXPECT().
ListProfiles(gomock.Any(), gomock.Any()).
Return(mockResp, nil)

return cli.WithRPCClient[minderv1.ProfileServiceClient](context.Background(), client)
},
GoldenFileName: "list_profiles.json",
},
{
Name: "list profiles yaml success",
Args: []string{"profile", "list", "-o", "yaml"},
Expand Down
20 changes: 0 additions & 20 deletions cmd/cli/app/profile/testdata/get_by_id.json.golden

This file was deleted.

60 changes: 0 additions & 60 deletions cmd/cli/app/profile/testdata/list_profiles.json.golden

This file was deleted.

Loading
Loading