Skip to content
Merged
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
55 changes: 46 additions & 9 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,56 @@ func newProjectNextCmd() *cobra.Command {
}

func newProjectRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove <name>",
removeAll := false
removeCmd := &cobra.Command{
Use: "remove [name]",
Short: "Remove configured project",
Args: cobra.ExactArgs(1),
Args: func(cmd *cobra.Command, args []string) error {
return validateProjectRemoveArgs(removeAll, cmd, args)
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := withConfigStore(func(store *config.Store) error {
return store.RemoveProject(args[0])
}); err != nil {
return fmt.Errorf("remove project: %w", err)
}
return nil
return runProjectRemove(removeAll, args)
},
}
removeCmd.Flags().BoolVar(&removeAll, "all", false, "Remove all configured projects and tokens")

return removeCmd
}

func validateProjectRemoveArgs(removeAll bool, cmd *cobra.Command, args []string) error {
if removeAll {
if len(args) > 0 {
return errors.New("cannot use --all with a project name")
}
return nil
}
if len(args) == 0 {
return errors.New("specify a project name or use --all")
}
if len(args) > 1 {
return cobra.MaximumNArgs(1)(cmd, args)
}

return nil
}

func runProjectRemove(removeAll bool, args []string) error {
if removeAll {
if err := withConfigStore(func(store *config.Store) error {
return store.RemoveAllProjects()
}); err != nil {
return fmt.Errorf("remove projects: %w", err)
}
return nil
}

if err := withConfigStore(func(store *config.Store) error {
return store.RemoveProject(args[0])
}); err != nil {
return fmt.Errorf("remove project: %w", err)
}

return nil
}

func runActive(parent context.Context, flags rootFlags) error {
Expand Down
53 changes: 53 additions & 0 deletions internal/cli/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,59 @@ func TestProjectCommandsUseNextRemove(t *testing.T) {
runRootCommand(t, "project", "remove", "beta")
}

func TestProjectRemoveAll(t *testing.T) {
configPath := filepath.Join(t.TempDir(), "config.json")
store := config.NewStoreAtPath(configPath)
if err := store.AddProject("alpha", "t1"); err != nil {
t.Fatalf("AddProject() error = %v", err)
}
if err := store.AddProject("beta", "t2"); err != nil {
t.Fatalf("AddProject() error = %v", err)
}

restoreStore := overrideConfigStore(func() (*config.Store, error) {
return store, nil
})
defer restoreStore()

runRootCommand(t, "project", "remove", "--all")

file, err := store.Load()
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if file.ActiveProject != "" {
t.Fatalf("ActiveProject = %q, want empty", file.ActiveProject)
}
if len(file.Projects) != 0 {
t.Fatalf("Projects length = %d, want 0", len(file.Projects))
}
}

func TestProjectRemoveValidation(t *testing.T) {
configPath := filepath.Join(t.TempDir(), "config.json")
store := config.NewStoreAtPath(configPath)

restoreStore := overrideConfigStore(func() (*config.Store, error) {
return store, nil
})
defer restoreStore()

cmd := NewRootCmd()
cmd.SetArgs([]string{"project", "remove"})
err := cmd.Execute()
if err == nil || !strings.Contains(err.Error(), "specify a project name or use --all") {
t.Fatalf("expected missing-args remove error, got %v", err)
}

cmd = NewRootCmd()
cmd.SetArgs([]string{"project", "remove", "alpha", "--all"})
err = cmd.Execute()
if err == nil || !strings.Contains(err.Error(), "cannot use --all with a project name") {
t.Fatalf("expected mixed remove args/flag error, got %v", err)
}
}

func TestRootCommandDefaultRunsActive(t *testing.T) {
stdout := setupServerAndStdout(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
Expand Down
4 changes: 4 additions & 0 deletions internal/config/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ func (s *Store) RemoveProject(name string) error {
return s.Save(file)
}

func (s *Store) RemoveAllProjects() error {
return s.Save(File{})
}

func (s *Store) UseProject(name string) error {
file, err := s.Load()
if err != nil {
Expand Down
31 changes: 31 additions & 0 deletions internal/config/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,37 @@ func TestStoreErrors(t *testing.T) {
}
}

func TestStoreRemoveAllProjects(t *testing.T) {
t.Parallel()

store, _ := newTempStore(t)
if err := store.AddProject("alpha", "token-a"); err != nil {
t.Fatalf("AddProject() error = %v", err)
}
if err := store.AddProject("beta", "token-b"); err != nil {
t.Fatalf("AddProject() error = %v", err)
}

if err := store.RemoveAllProjects(); err != nil {
t.Fatalf("RemoveAllProjects() error = %v", err)
}

file, err := store.Load()
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if file.ActiveProject != "" {
t.Fatalf("ActiveProject = %q, want empty", file.ActiveProject)
}
if len(file.Projects) != 0 {
t.Fatalf("Projects length = %d, want 0", len(file.Projects))
}

if _, _, err := store.ResolveToken(""); err == nil {
t.Fatalf("expected no configured projects error")
}
}

func TestStoreAddProjectUpdatesExisting(t *testing.T) {
t.Parallel()

Expand Down