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
1,431 changes: 1,431 additions & 0 deletions cmd/cmd_test.go

Large diffs are not rendered by default.

25 changes: 14 additions & 11 deletions cmd/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package cmd

import (
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/adrianpk/snapfig/internal/snapfig"
)

var copyCmd = &cobra.Command{
Expand All @@ -19,36 +18,40 @@ func init() {
rootCmd.AddCommand(copyCmd)
}

// runCopy delegates to runCopyWithOutput which is unit tested.
func runCopy(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
return runCopyWithOutput(cmd.OutOrStdout())
}

func runCopyWithOutput(w io.Writer) error {
cfg, configPath, err := loadConfigWithPath()
if err != nil {
return err
}

if len(cfg.Watching) == 0 {
fmt.Println("No paths configured. Run 'snapfig' to select paths.")
fmt.Fprintln(w, "No paths configured. Run 'snapfig' to select paths.")
return nil
}

copier, err := snapfig.NewCopier(cfg)
svc, err := ServiceFactory(cfg, configPath)
if err != nil {
return err
}

fmt.Println("Copying to vault...")
result, err := copier.Copy()
fmt.Fprintln(w, "Copying to vault...")
result, err := svc.Copy()
if err != nil {
return err
}

for _, p := range result.Copied {
fmt.Printf(" Copied: %s\n", p)
fmt.Fprintf(w, " Copied: %s\n", p)
}
for _, p := range result.Skipped {
fmt.Printf(" Skipped: %s (not found)\n", p)
fmt.Fprintf(w, " Skipped: %s (not found)\n", p)
}

vaultDir, _ := cfg.VaultDir()
fmt.Printf("\nDone. %d copied, %d skipped. Vault: %s\n", len(result.Copied), len(result.Skipped), vaultDir)
fmt.Fprintf(w, "\nDone. %d copied, %d skipped. Vault: %s\n", len(result.Copied), len(result.Skipped), svc.VaultDir())
return nil
}
21 changes: 12 additions & 9 deletions cmd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/spf13/cobra"

"github.com/adrianpk/snapfig/internal/config"
"github.com/adrianpk/snapfig/internal/daemon"
)

Expand Down Expand Up @@ -59,12 +58,12 @@ func runDaemonStart(cmd *cobra.Command, args []string) error {
}

// Get paths
logPath, err := config.LogFilePath()
logPath, err := LogFilePathFunc()
if err != nil {
return err
}

snapfigDir, err := config.DefaultSnapfigDir()
snapfigDir, err := DefaultSnapfigDirFunc()
if err != nil {
return err
}
Expand Down Expand Up @@ -99,6 +98,8 @@ func runDaemonStart(cmd *cobra.Command, args []string) error {
return nil
}

// runDaemonStop terminates a running daemon process via SIGTERM.
// Requires running daemon process; signal handling not unit testable.
func runDaemonStop(cmd *cobra.Command, args []string) error {
pid, running := getDaemonPid()
if !running {
Expand All @@ -116,7 +117,7 @@ func runDaemonStop(cmd *cobra.Command, args []string) error {
}

// Remove PID file
pidPath, _ := config.PidFilePath()
pidPath, _ := PidFilePathFunc()
os.Remove(pidPath)

fmt.Printf("Daemon stopped (pid %d)\n", pid)
Expand All @@ -133,9 +134,9 @@ func runDaemonStatus(cmd *cobra.Command, args []string) error {
fmt.Printf("Daemon is running (pid %d)\n", pid)

// Load config to show intervals
configDir, _ := config.DefaultConfigDir()
configDir, _ := DefaultConfigDirFunc()
configPath := filepath.Join(configDir, "config.yml")
cfg, err := config.Load(configPath)
cfg, err := ConfigLoader(configPath)
if err == nil && cfg.Daemon.CopyInterval != "" {
fmt.Printf(" Copy interval: %s\n", cfg.Daemon.CopyInterval)
if cfg.Daemon.PushInterval != "" {
Expand All @@ -150,14 +151,16 @@ func runDaemonStatus(cmd *cobra.Command, args []string) error {
return nil
}

// runDaemonForeground runs the daemon in the foreground (blocking).
// Runs blocking signal loop; daemon logic tested in daemon_test.go.
func runDaemonForeground(cmd *cobra.Command, args []string) error {
configDir, err := config.DefaultConfigDir()
configDir, err := DefaultConfigDirFunc()
if err != nil {
return err
}
configPath := filepath.Join(configDir, "config.yml")

cfg, err := config.Load(configPath)
cfg, err := ConfigLoader(configPath)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
Expand All @@ -171,7 +174,7 @@ func runDaemonForeground(cmd *cobra.Command, args []string) error {
}

func getDaemonPid() (int, bool) {
pidPath, err := config.PidFilePath()
pidPath, err := PidFilePathFunc()
if err != nil {
return 0, false
}
Expand Down
51 changes: 51 additions & 0 deletions cmd/deps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Package cmd implements the CLI commands.
package cmd

import (
"github.com/adrianpk/snapfig/internal/config"
"github.com/adrianpk/snapfig/internal/snapfig"
)

// ServiceFactory creates a Service from config and path.
// This can be replaced in tests for mocking.
var ServiceFactory = func(cfg *config.Config, configPath string) (snapfig.Service, error) {
return snapfig.NewService(cfg, configPath)
}

// ConfigLoader loads config from a path.
// This can be replaced in tests for mocking.
var ConfigLoader = config.Load

// DefaultConfigDirFunc returns the default config directory.
// This can be replaced in tests for mocking.
var DefaultConfigDirFunc = config.DefaultConfigDir

// HasRemoteFunc checks if a vault directory has a git remote configured.
// This can be replaced in tests for mocking.
var HasRemoteFunc = snapfig.HasRemote

// PidFilePathFunc returns the path to the daemon PID file.
// This can be replaced in tests for mocking.
var PidFilePathFunc = config.PidFilePath

// LogFilePathFunc returns the path to the daemon log file.
// This can be replaced in tests for mocking.
var LogFilePathFunc = config.LogFilePath

// DefaultSnapfigDirFunc returns the default snapfig directory.
// This can be replaced in tests for mocking.
var DefaultSnapfigDirFunc = config.DefaultSnapfigDir

// resetDeps resets all dependencies to their defaults.
// Used in tests to ensure clean state.
func resetDeps() {
ServiceFactory = func(cfg *config.Config, configPath string) (snapfig.Service, error) {
return snapfig.NewService(cfg, configPath)
}
ConfigLoader = config.Load
DefaultConfigDirFunc = config.DefaultConfigDir
HasRemoteFunc = snapfig.HasRemote
PidFilePathFunc = config.PidFilePath
LogFilePathFunc = config.LogFilePath
DefaultSnapfigDirFunc = config.DefaultSnapfigDir
}
22 changes: 13 additions & 9 deletions cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package cmd

import (
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/adrianpk/snapfig/internal/snapfig"
)

var pullCmd = &cobra.Command{
Expand All @@ -19,21 +18,26 @@ func init() {
rootCmd.AddCommand(pullCmd)
}

// runPull delegates to runPullWithOutput which is unit tested.
func runPull(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
return runPullWithOutput(cmd.OutOrStdout())
}

func runPullWithOutput(w io.Writer) error {
cfg, configPath, err := loadConfigWithPath()
if err != nil {
return err
}

vaultDir, err := cfg.VaultDir()
svc, err := ServiceFactory(cfg, configPath)
if err != nil {
return err
}

remoteURL := cfg.Remote
if remoteURL == "" {
// Try to get from git
hasRemote, url, err := snapfig.HasRemote(vaultDir)
hasRemote, url, err := HasRemoteFunc(svc.VaultDir())
if err != nil {
return err
}
Expand All @@ -43,16 +47,16 @@ func runPull(cmd *cobra.Command, args []string) error {
remoteURL = url
}

fmt.Printf("Pulling from %s...\n", remoteURL)
result, err := snapfig.PullVaultWithToken(vaultDir, remoteURL, cfg.GitToken)
fmt.Fprintf(w, "Pulling from %s...\n", remoteURL)
result, err := svc.Pull()
if err != nil {
return err
}

if result.Cloned {
fmt.Println("Cloned successfully.")
fmt.Fprintln(w, "Cloned successfully.")
} else {
fmt.Println("Pulled successfully.")
fmt.Fprintln(w, "Pulled successfully.")
}
return nil
}
22 changes: 13 additions & 9 deletions cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package cmd

import (
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/adrianpk/snapfig/internal/snapfig"
)

var pushCmd = &cobra.Command{
Expand All @@ -19,30 +18,35 @@ func init() {
rootCmd.AddCommand(pushCmd)
}

// runPush delegates to runPushWithOutput which is unit tested.
func runPush(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
return runPushWithOutput(cmd.OutOrStdout())
}

func runPushWithOutput(w io.Writer) error {
cfg, configPath, err := loadConfigWithPath()
if err != nil {
return err
}

vaultDir, err := cfg.VaultDir()
svc, err := ServiceFactory(cfg, configPath)
if err != nil {
return err
}

hasRemote, url, err := snapfig.HasRemote(vaultDir)
hasRemote, url, err := HasRemoteFunc(svc.VaultDir())
if err != nil {
return err
}
if !hasRemote {
return fmt.Errorf("no remote configured. Run: cd %s && git remote add origin <url>", vaultDir)
return fmt.Errorf("no remote configured. Run: cd %s && git remote add origin <url>", svc.VaultDir())
}

fmt.Printf("Pushing to %s...\n", url)
if err := snapfig.PushVaultWithToken(vaultDir, cfg.GitToken); err != nil {
fmt.Fprintf(w, "Pushing to %s...\n", url)
if err := svc.Push(); err != nil {
return err
}

fmt.Println("Done.")
fmt.Fprintln(w, "Done.")
return nil
}
34 changes: 15 additions & 19 deletions cmd/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ package cmd

import (
"fmt"
"path/filepath"
"io"

"github.com/spf13/cobra"

"github.com/adrianpk/snapfig/internal/config"
"github.com/adrianpk/snapfig/internal/snapfig"
)

var restoreCmd = &cobra.Command{
Expand All @@ -21,45 +18,44 @@ func init() {
rootCmd.AddCommand(restoreCmd)
}

// runRestore delegates to runRestoreWithOutput which is unit tested.
func runRestore(cmd *cobra.Command, args []string) error {
configDir, err := config.DefaultConfigDir()
if err != nil {
return fmt.Errorf("failed to get config directory: %w", err)
}
configPath := filepath.Join(configDir, "config.yml")
return runRestoreWithOutput(cmd.OutOrStdout())
}

cfg, err := config.Load(configPath)
func runRestoreWithOutput(w io.Writer) error {
cfg, configPath, err := loadConfigWithPath()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return err
}

if len(cfg.Watching) == 0 {
fmt.Println("No paths configured. Run 'snapfig tui' to select paths.")
fmt.Fprintln(w, "No paths configured. Run 'snapfig tui' to select paths.")
return nil
}

restorer, err := snapfig.NewRestorer(cfg)
svc, err := ServiceFactory(cfg, configPath)
if err != nil {
return err
}

fmt.Println("Restoring from vault...")
result, err := restorer.Restore()
fmt.Fprintln(w, "Restoring from vault...")
result, err := svc.Restore()
if err != nil {
return err
}

for _, p := range result.Backups {
fmt.Printf(" Backed up: %s\n", p)
fmt.Fprintf(w, " Backed up: %s\n", p)
}
for _, p := range result.Restored {
fmt.Printf(" Restored: %s\n", p)
fmt.Fprintf(w, " Restored: %s\n", p)
}
for _, p := range result.Skipped {
fmt.Printf(" Skipped: %s (not in vault)\n", p)
fmt.Fprintf(w, " Skipped: %s (not in vault)\n", p)
}

fmt.Printf("\nDone. %d restored, %d backed up, %d skipped.\n",
fmt.Fprintf(w, "\nDone. %d restored, %d backed up, %d skipped.\n",
len(result.Restored), len(result.Backups), len(result.Skipped))
return nil
}
Loading
Loading