mirror of
https://github.com/chenasraf/cospend-cli.git
synced 2026-05-17 17:38:04 +00:00
feat: add logout command
This commit is contained in:
10
README.md
10
README.md
@@ -408,6 +408,16 @@ cospend config get default-project
|
||||
|
||||
---
|
||||
|
||||
### Logging Out
|
||||
|
||||
```bash
|
||||
cospend logout
|
||||
```
|
||||
|
||||
Removes the configuration file (stored credentials) and clears all cached data.
|
||||
|
||||
---
|
||||
|
||||
## Caching
|
||||
|
||||
Project data (members, categories, payment methods, currencies) is cached locally to avoid repeated
|
||||
|
||||
58
cmd/logout.go
Normal file
58
cmd/logout.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/chenasraf/cospend-cli/internal/cache"
|
||||
"github.com/chenasraf/cospend-cli/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewLogoutCommand creates the logout command
|
||||
func NewLogoutCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Remove configuration and cached data",
|
||||
Long: `Remove the configuration file and clear all cached data.
|
||||
|
||||
This effectively logs you out by removing your stored credentials
|
||||
and any cached project data.`,
|
||||
RunE: runLogout,
|
||||
}
|
||||
}
|
||||
|
||||
func runLogout(cmd *cobra.Command, _ []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
removed := false
|
||||
|
||||
// Remove config file
|
||||
configPath := config.GetConfigPath()
|
||||
if configPath != "" {
|
||||
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("removing config file: %w", err)
|
||||
}
|
||||
_, _ = fmt.Fprintf(out, "Removed config: %s\n", configPath)
|
||||
removed = true
|
||||
}
|
||||
|
||||
// Remove cache directory
|
||||
cacheDir := cache.GetCacheDir()
|
||||
if info, err := os.Stat(cacheDir); err == nil && info.IsDir() {
|
||||
if err := os.RemoveAll(cacheDir); err != nil {
|
||||
return fmt.Errorf("removing cache directory: %w", err)
|
||||
}
|
||||
_, _ = fmt.Fprintf(out, "Removed cache: %s\n", cacheDir)
|
||||
removed = true
|
||||
}
|
||||
|
||||
if !removed {
|
||||
_, _ = fmt.Fprintln(out, "Nothing to remove (no config or cache found).")
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(out, "Logged out successfully.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
128
cmd/logout_test.go
Normal file
128
cmd/logout_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewLogoutCommand(t *testing.T) {
|
||||
cmd := NewLogoutCommand()
|
||||
if cmd.Use != "logout" {
|
||||
t.Errorf("Wrong Use: %s", cmd.Use)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutRemovesConfigAndCache(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
t.Setenv("XDG_CACHE_HOME", tempDir)
|
||||
t.Setenv("HOME", tempDir)
|
||||
|
||||
// Create config file
|
||||
configDir := filepath.Join(tempDir, "cospend")
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("Failed to create config dir: %v", err)
|
||||
}
|
||||
configPath := filepath.Join(configDir, "cospend.json")
|
||||
if err := os.WriteFile(configPath, []byte(`{"domain":"x","user":"u","password":"p"}`), 0600); err != nil {
|
||||
t.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Create cache directory with a file in a separate temp dir
|
||||
cacheTempDir := t.TempDir()
|
||||
t.Setenv("XDG_CACHE_HOME", cacheTempDir)
|
||||
cacheDir := filepath.Join(cacheTempDir, "cospend")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create cache dir: %v", err)
|
||||
}
|
||||
cachePath := filepath.Join(cacheDir, "test-project.json")
|
||||
if err := os.WriteFile(cachePath, []byte(`{}`), 0600); err != nil {
|
||||
t.Fatalf("Failed to write cache: %v", err)
|
||||
}
|
||||
|
||||
cmd := NewLogoutCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Config should be removed
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
t.Error("Config file should have been removed")
|
||||
}
|
||||
|
||||
// Cache dir should be removed
|
||||
if _, err := os.Stat(cacheDir); !os.IsNotExist(err) {
|
||||
t.Error("Cache directory should have been removed")
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
if !bytes.Contains([]byte(output), []byte("Logged out successfully")) {
|
||||
t.Errorf("Expected success message, got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutNothingToRemove(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
t.Setenv("XDG_CACHE_HOME", tempDir)
|
||||
t.Setenv("HOME", tempDir)
|
||||
|
||||
cmd := NewLogoutCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
if !bytes.Contains([]byte(output), []byte("Nothing to remove")) {
|
||||
t.Errorf("Expected 'Nothing to remove' message, got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutConfigOnly(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
cacheTempDir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
t.Setenv("XDG_CACHE_HOME", cacheTempDir)
|
||||
t.Setenv("HOME", tempDir)
|
||||
|
||||
// Create config file only (no cache)
|
||||
configDir := filepath.Join(tempDir, "cospend")
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("Failed to create config dir: %v", err)
|
||||
}
|
||||
configPath := filepath.Join(configDir, "cospend.json")
|
||||
if err := os.WriteFile(configPath, []byte(`{"domain":"x","user":"u","password":"p"}`), 0600); err != nil {
|
||||
t.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
cmd := NewLogoutCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
t.Error("Config file should have been removed")
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
if !bytes.Contains([]byte(output), []byte("Removed config")) {
|
||||
t.Errorf("Expected 'Removed config' message, got: %s", output)
|
||||
}
|
||||
if !bytes.Contains([]byte(output), []byte("Logged out successfully")) {
|
||||
t.Errorf("Expected success message, got: %s", output)
|
||||
}
|
||||
}
|
||||
5
internal/cache/cache.go
vendored
5
internal/cache/cache.go
vendored
@@ -149,6 +149,11 @@ func getCacheHome() string {
|
||||
return xdg.CacheHome
|
||||
}
|
||||
|
||||
// GetCacheDir returns the cache directory path
|
||||
func GetCacheDir() string {
|
||||
return filepath.Join(getCacheHome(), appName)
|
||||
}
|
||||
|
||||
// getCachePath returns the cache file path for a project
|
||||
func getCachePath(projectID string) (string, error) {
|
||||
cacheDir := filepath.Join(getCacheHome(), appName)
|
||||
|
||||
1
main.go
1
main.go
@@ -38,6 +38,7 @@ func main() {
|
||||
rootCmd.AddCommand(cmd.NewProjectsCommand())
|
||||
rootCmd.AddCommand(cmd.NewInfoCommand())
|
||||
rootCmd.AddCommand(cmd.NewConfigCommand())
|
||||
rootCmd.AddCommand(cmd.NewLogoutCommand())
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&cmd.Debug, "debug", "D", false, "Enable debug output")
|
||||
rootCmd.PersistentFlags().StringVarP(&cmd.ProjectID, "project", "p", "", "Project ID")
|
||||
|
||||
Reference in New Issue
Block a user