From 7ff4c5c1908a806cd9bbefb110504260de55eb0b Mon Sep 17 00:00:00 2001 From: Ilyaas Kapadia <86218345+IlyaasK@users.noreply.github.com> Date: Fri, 29 May 2026 17:58:08 -0400 Subject: [PATCH] Add projects update command --- cmd/projects.go | 63 ++++++++++++++++++++++ cmd/projects_test.go | 123 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/cmd/projects.go b/cmd/projects.go index 35fe932..54bdc4d 100644 --- a/cmd/projects.go +++ b/cmd/projects.go @@ -22,6 +22,7 @@ type ProjectsService interface { ProjectListService New(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (res *kernel.Project, err error) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.Project, err error) + Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (res *kernel.Project, err error) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) } @@ -48,6 +49,12 @@ type ProjectsGetInput struct { Output string } +type ProjectsUpdateInput struct { + Identifier string + Name string + Output string +} + type ProjectsDeleteInput struct { Identifier string } @@ -155,6 +162,40 @@ func (c ProjectsCmd) Get(ctx context.Context, in ProjectsGetInput) error { return nil } +func (c ProjectsCmd) Update(ctx context.Context, in ProjectsUpdateInput) error { + if err := validateJSONOutput(in.Output); err != nil { + return err + } + if in.Name == "" { + return util.RequiredFlag("--name", "") + } + + projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier) + if err != nil { + return err + } + + project, err := c.projects.Update(ctx, projectID, kernel.ProjectUpdateParams{ + UpdateProjectRequest: kernel.UpdateProjectRequestParam{ + Name: kernel.String(in.Name), + }, + }) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + if project == nil { + fmt.Println("null") + return nil + } + return util.PrintPrettyJSON(project) + } + + pterm.Success.Printf("Updated project: %s (ID: %s)\n", project.Name, project.ID) + return nil +} + func (c ProjectsCmd) Delete(ctx context.Context, in ProjectsDeleteInput) error { projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier) if err != nil { @@ -297,6 +338,17 @@ func runProjectsGet(cmd *cobra.Command, args []string) error { return c.Get(cmd.Context(), ProjectsGetInput{Identifier: args[0], Output: output}) } +func runProjectsUpdate(cmd *cobra.Command, args []string) error { + c := getProjectsHandler(cmd) + name, _ := cmd.Flags().GetString("name") + output, _ := cmd.Flags().GetString("output") + return c.Update(cmd.Context(), ProjectsUpdateInput{ + Identifier: args[0], + Name: name, + Output: output, + }) +} + func runProjectsDelete(cmd *cobra.Command, args []string) error { c := getProjectsHandler(cmd) return c.Delete(cmd.Context(), ProjectsDeleteInput{Identifier: args[0]}) @@ -371,6 +423,13 @@ var projectsGetCmd = &cobra.Command{ RunE: runProjectsGet, } +var projectsUpdateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a project", + Args: cobra.ExactArgs(1), + RunE: runProjectsUpdate, +} + var projectsDeleteCmd = &cobra.Command{ Use: "delete ", Short: "Delete a project", @@ -419,6 +478,9 @@ var projectsSetLimitsCompatCmd = &cobra.Command{ func init() { addJSONOutputFlag(projectsListCmd) addJSONOutputFlag(projectsGetCmd) + addJSONOutputFlag(projectsUpdateCmd) + projectsUpdateCmd.Flags().String("name", "", "New project name (required)") + _ = projectsUpdateCmd.MarkFlagRequired("name") addJSONOutputFlag(projectsLimitsGetCmd) addProjectsLimitsSetFlags(projectsLimitsSetCmd) addJSONOutputFlag(projectsGetLimitsCompatCmd) @@ -430,6 +492,7 @@ func init() { projectsCmd.AddCommand(projectsListCmd) projectsCmd.AddCommand(projectsCreateCmd) projectsCmd.AddCommand(projectsGetCmd) + projectsCmd.AddCommand(projectsUpdateCmd) projectsCmd.AddCommand(projectsDeleteCmd) projectsCmd.AddCommand(projectsLimitsCmd) projectsCmd.AddCommand(projectsGetLimitsCompatCmd) diff --git a/cmd/projects_test.go b/cmd/projects_test.go index 1e085c8..ec70c6d 100644 --- a/cmd/projects_test.go +++ b/cmd/projects_test.go @@ -21,6 +21,7 @@ type FakeProjectsService struct { ListFunc func(ctx context.Context, query kernel.ProjectListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Project], error) NewFunc func(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (*kernel.Project, error) GetFunc func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.Project, error) + UpdateFunc func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) DeleteFunc func(ctx context.Context, id string, opts ...option.RequestOption) error } @@ -45,6 +46,13 @@ func (f *FakeProjectsService) Get(ctx context.Context, id string, opts ...option return &kernel.Project{ID: id, Name: "default"}, nil } +func (f *FakeProjectsService) Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + if f.UpdateFunc != nil { + return f.UpdateFunc(ctx, id, body, opts...) + } + return &kernel.Project{ID: id, Name: body.UpdateProjectRequest.Name.Value}, nil +} + func (f *FakeProjectsService) Delete(ctx context.Context, id string, opts ...option.RequestOption) error { if f.DeleteFunc != nil { return f.DeleteFunc(ctx, id, opts...) @@ -146,6 +154,121 @@ func TestProjectsGet_InvalidOutput(t *testing.T) { assert.Contains(t, err.Error(), "unsupported --output value") } +func TestProjectsUpdate_Success(t *testing.T) { + buf := capturePtermOutput(t) + c := ProjectsCmd{ + projects: &FakeProjectsService{ + UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + assert.Equal(t, "a12345678901234567890123", id) + assert.True(t, body.UpdateProjectRequest.Name.Valid()) + assert.Equal(t, "Renamed", body.UpdateProjectRequest.Name.Value) + return &kernel.Project{ID: id, Name: body.UpdateProjectRequest.Name.Value}, nil + }, + }, + limits: &FakeProjectLimitsService{}, + } + + err := c.Update(context.Background(), ProjectsUpdateInput{ + Identifier: "a12345678901234567890123", + Name: "Renamed", + }) + + require.NoError(t, err) + out := buf.String() + assert.Contains(t, out, "Updated project: Renamed") + assert.Contains(t, out, "a12345678901234567890123") +} + +func TestProjectsUpdate_JSONOutput(t *testing.T) { + project := mustProject(t, `{"id":"a12345678901234567890123","name":"Renamed","status":"active","created_at":"2026-05-29T12:00:00Z","updated_at":"2026-05-29T12:30:00Z"}`) + c := ProjectsCmd{ + projects: &FakeProjectsService{ + UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + assert.Equal(t, "a12345678901234567890123", id) + assert.True(t, body.UpdateProjectRequest.Name.Valid()) + assert.Equal(t, "Renamed", body.UpdateProjectRequest.Name.Value) + return &project, nil + }, + }, + limits: &FakeProjectLimitsService{}, + } + + var err error + out := captureStdout(t, func() { + err = c.Update(context.Background(), ProjectsUpdateInput{ + Identifier: "a12345678901234567890123", + Name: "Renamed", + Output: "json", + }) + }) + + require.NoError(t, err) + assert.JSONEq(t, `{"id":"a12345678901234567890123","name":"Renamed","status":"active","created_at":"2026-05-29T12:00:00Z","updated_at":"2026-05-29T12:30:00Z"}`, out) +} + +func TestProjectsUpdate_ResolvesName(t *testing.T) { + c := ProjectsCmd{ + projects: &FakeProjectsService{ + ListFunc: func(ctx context.Context, query kernel.ProjectListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Project], error) { + return &pagination.OffsetPagination[kernel.Project]{ + Items: []kernel.Project{{ID: "a12345678901234567890123", Name: "Default"}}, + }, nil + }, + UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + assert.Equal(t, "a12345678901234567890123", id) + return &kernel.Project{ID: id, Name: body.UpdateProjectRequest.Name.Value}, nil + }, + }, + limits: &FakeProjectLimitsService{}, + } + + err := c.Update(context.Background(), ProjectsUpdateInput{ + Identifier: "default", + Name: "Renamed", + }) + + require.NoError(t, err) +} + +func TestProjectsUpdate_RequiresName(t *testing.T) { + c := ProjectsCmd{ + projects: &FakeProjectsService{ + UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + t.Fatal("Update should not be called") + return nil, nil + }, + }, + limits: &FakeProjectLimitsService{}, + } + + err := c.Update(context.Background(), ProjectsUpdateInput{Identifier: "a12345678901234567890123"}) + + require.Error(t, err) + assert.Contains(t, err.Error(), "--name is required") + assert.Contains(t, err.Error(), "add --name ") +} + +func TestProjectsUpdate_InvalidOutput(t *testing.T) { + c := ProjectsCmd{ + projects: &FakeProjectsService{ + UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) { + t.Fatal("Update should not be called") + return nil, nil + }, + }, + limits: &FakeProjectLimitsService{}, + } + + err := c.Update(context.Background(), ProjectsUpdateInput{ + Identifier: "a12345678901234567890123", + Name: "Renamed", + Output: "yaml", + }) + + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported --output value") +} + func TestProjectsLimitsGet_DefaultOutput(t *testing.T) { buf := capturePtermOutput(t) limits := &kernel.ProjectLimits{