Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions cmd/cli/app/project/fixture/mock_project_create.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"project": {
"projectId": "12345678-1234-1234-1234-123456789012",
"name": "mock-project",
"description": "A newly created mock project",
"createdAt": "2026-04-24T14:38:18.810108Z",
"updatedAt": "2026-04-24T14:38:18.810108Z",
"displayName": "mock-project"
}
}
3 changes: 3 additions & 0 deletions cmd/cli/app/project/fixture/mock_project_delete.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"projectId": "12345678-1234-1234-1234-123456789012"
}
28 changes: 28 additions & 0 deletions cmd/cli/app/project/fixture/mock_project_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"projects": [
{
"projectId": "00000000-0000-0000-0000-000000000001",
"name": "mock-project-alpha",
"description": "First mock project for testing.",
"createdAt": "2024-01-01T12:00:00.000000Z",
"updatedAt": "2024-01-01T12:00:00.000000Z",
"displayName": "mock-project-alpha"
},
{
"projectId": "00000000-0000-0000-0000-000000000002",
"name": "mock-project-beta",
"description": "Second mock project for testing.",
"createdAt": "2024-01-02T12:00:00.000000Z",
"updatedAt": "2024-01-02T12:00:00.000000Z",
"displayName": "mock-project-beta"
},
{
"projectId": "00000000-0000-0000-0000-000000000003",
"name": "mock-project-gamma",
"description": "Third mock project for testing.",
"createdAt": "2024-01-03T12:00:00.000000Z",
"updatedAt": "2024-01-03T12:00:00.000000Z",
"displayName": "mock-project-gamma"
}
]
}
24 changes: 16 additions & 8 deletions cmd/cli/app/project/project_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
package project

import (
"context"
"fmt"
"strings"

"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 @@ -24,13 +22,23 @@ import (
var projectCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a sub-project within a minder control plane",
Long: `The list command lists the projects available to you within a minder control plane.`,
RunE: cli.GRPCClientWrapRunE(createCommand),
Long: `The create command creates a sub-project within a minder control plane.`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return cli.MessageAndError("Error binding flags", err)
}
return nil
},
RunE: createCommand,
}

// listCommand is the command for listing projects
func createCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error {
client := minderv1.NewProjectsServiceClient(conn)
// createCommand is the command for listing projects

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.

Suggested change
// createCommand is the command for listing projects
// createCommand is the command for creating a new project

func createCommand(cmd *cobra.Command, _ []string) error {
client, cleanup, err := GetProjectsClient(cmd)

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.

Switch to cli.GetGRPCClient

if err != nil {
return cli.MessageAndError("Error getting client", err)
}
defer cleanup()

format := viper.GetString("output")
project := viper.GetString("project")
Expand All @@ -40,7 +48,7 @@ func createCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *gr
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

resp, err := client.CreateProject(ctx, &minderv1.CreateProjectRequest{
resp, err := client.CreateProject(cmd.Context(), &minderv1.CreateProjectRequest{
Context: &minderv1.Context{
Project: &project,
},
Expand Down
106 changes: 106 additions & 0 deletions cmd/cli/app/project/project_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package project

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 TestProjectCreateCommand(t *testing.T) {
const (
projectName = "mock-project"
)
tests := []cli.CmdTestCase{
{
Name: "create project table output",
Args: []string{"project", "create", "-n", projectName, "-o", "table"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

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

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

return cli.WithRPCClient[minderv1.ProjectsServiceClient](context.Background(), client)
},
GoldenFileName: "create_table.txt",
},
{
Name: "create project json output",
Args: []string{"project", "create", "-n", projectName, "-o", "json"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

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

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

return cli.WithRPCClient[minderv1.ProjectsServiceClient](context.Background(), client)
},
GoldenFileName: "create_json.txt",
},
{
Name: "create project yaml output",
Args: []string{"project", "create", "-n", projectName, "-o", "yaml"},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

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

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

return cli.WithRPCClient[minderv1.ProjectsServiceClient](context.Background(), client)
},
GoldenFileName: "create_yaml.txt",
},
{
Name: "fails when missing name flag",
Args: []string{"project", "create"},
ExpectedError: "required flag(s) \"name\" not set",
},
{
Name: "server error handling",
Args: []string{"project", "create", "-n", projectName},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

client.EXPECT().
CreateProject(gomock.Any(), gomock.Any()).
Return(nil, status.Error(codes.Internal, "internal server error")).
Times(1)

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

cli.RunCmdTests(t, tests, ProjectCmd)
}
23 changes: 15 additions & 8 deletions cmd/cli/app/project/project_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
package project

import (
"context"

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

"github.com/mindersec/minder/internal/util/cli"
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
Expand All @@ -19,20 +16,30 @@ var projectDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a sub-project within a minder control plane",
Long: `Delete a sub-project within a minder control plane`,
RunE: cli.GRPCClientWrapRunE(deleteCommand),
PreRunE: func(cmd *cobra.Command, _ []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return cli.MessageAndError("Error binding flags", err)
}
return nil
},
RunE: deleteCommand,
}

// listCommand is the command for listing projects
func deleteCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error {
client := minderv1.NewProjectsServiceClient(conn)
// deleteCommand is the command for listing projects

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.

Suggested change
// deleteCommand is the command for listing projects
// deleteCommand is the command to permanently delete a project

func deleteCommand(cmd *cobra.Command, _ []string) error {
client, cleanup, err := GetProjectsClient(cmd)
if err != nil {
return cli.MessageAndError("Error getting client", err)
}
defer cleanup()

project := viper.GetString("project")

// 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

resp, err := client.DeleteProject(ctx, &minderv1.DeleteProjectRequest{
resp, err := client.DeleteProject(cmd.Context(), &minderv1.DeleteProjectRequest{
Context: &minderv1.Context{
Project: &project,
},
Expand Down
69 changes: 69 additions & 0 deletions cmd/cli/app/project/project_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package project

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 TestProjectDeleteCommand(t *testing.T) {
const (
projectID = "12345678-1234-1234-1234-123456789012"
)

tests := []cli.CmdTestCase{
{
Name: "delete project success",
Args: []string{"project", "delete", "-j", projectID},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

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

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

return cli.WithRPCClient[minderv1.ProjectsServiceClient](context.Background(), client)
},
GoldenFileName: "delete_success.txt",
},
{
Name: "fails when missing project flag",
Args: []string{"project", "delete"},
ExpectedError: "required flag(s) \"project\" not set",
},
{
Name: "server error handling",
Args: []string{"project", "delete", "-j", projectID},
MockSetup: func(t *testing.T, ctrl *gomock.Controller) context.Context {
t.Helper()
client := mockv1.NewMockProjectsServiceClient(ctrl)

client.EXPECT().
DeleteProject(gomock.Any(), gomock.Any()).
Return(nil, status.Error(codes.Internal, "internal server error")).
Times(1)

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

cli.RunCmdTests(t, tests, ProjectCmd)
}
20 changes: 14 additions & 6 deletions cmd/cli/app/project/project_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
package project

import (
"context"
"fmt"
"strings"

"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,20 +23,30 @@ var projectListCmd = &cobra.Command{
Use: "list",
Short: "List the projects available to you within a minder control plane",
Long: `The list command lists the projects available to you within a minder control plane.`,
RunE: cli.GRPCClientWrapRunE(listCommand),
PreRunE: func(cmd *cobra.Command, _ []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return cli.MessageAndError("Error binding flags", err)
}
return nil
},
RunE: listCommand,
}

// listCommand is the command for listing projects
func listCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error {
client := minderv1.NewProjectsServiceClient(conn)
func listCommand(cmd *cobra.Command, _ []string) error {
client, cleanup, err := GetProjectsClient(cmd)
if err != nil {
return cli.MessageAndError("Error getting client", err)
}
defer cleanup()

format := viper.GetString("output")

// 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

resp, err := client.ListProjects(ctx, &minderv1.ListProjectsRequest{})
resp, err := client.ListProjects(cmd.Context(), &minderv1.ListProjectsRequest{})
if err != nil {
return cli.MessageAndError("Error listing projects", err)
}
Expand Down
Loading
Loading