/ cmd / sessions_test.go
sessions_test.go
 1  package cmd
 2  
 3  import (
 4  	"bytes"
 5  	"os"
 6  	"path/filepath"
 7  	"strings"
 8  	"testing"
 9  
10  	"github.com/spf13/viper"
11  
12  	"github.com/Kocoro-lab/ShanClaw/internal/config"
13  )
14  
15  // TestSessionsSync_ReadsConfigViaCobra is the regression guard for the class
16  // of bug where a cobra subcommand RunE forgets to call config.Load() and
17  // silently runs on an uninitialized viper. Before the fix, this test would
18  // see "sync is disabled" on stdout regardless of the yaml file because viper
19  // returned the SetDefault value of `sync.enabled=false`. After the fix, the
20  // yaml `sync.enabled: true` flows through and the dry-run codepath runs to
21  // completion, emitting a `sync: outcome=noop ...` summary instead.
22  func TestSessionsSync_ReadsConfigViaCobra(t *testing.T) {
23  	home := t.TempDir()
24  	shannonDir := filepath.Join(home, ".shannon")
25  	if err := os.MkdirAll(shannonDir, 0700); err != nil {
26  		t.Fatalf("mkdir shannon dir: %v", err)
27  	}
28  	cfgYAML := "sync:\n  enabled: true\n  dry_run: true\n"
29  	if err := os.WriteFile(filepath.Join(shannonDir, "config.yaml"), []byte(cfgYAML), 0600); err != nil {
30  		t.Fatalf("write config: %v", err)
31  	}
32  
33  	withIsolatedEnv(t, home)
34  	viper.Reset()
35  
36  	var stdout, stderr bytes.Buffer
37  	rootCmd.SetOut(&stdout)
38  	rootCmd.SetErr(&stderr)
39  	rootCmd.SetArgs([]string{"sessions", "sync"})
40  	t.Cleanup(func() {
41  		rootCmd.SetArgs(nil)
42  		rootCmd.SetOut(nil)
43  		rootCmd.SetErr(nil)
44  	})
45  
46  	if err := rootCmd.Execute(); err != nil {
47  		t.Fatalf("rootCmd.Execute: %v (stderr=%q)", err, stderr.String())
48  	}
49  
50  	out := stdout.String()
51  	if strings.Contains(out, "sync is disabled") {
52  		t.Fatalf("config not loaded — Bug 1 regression; stdout=%q", out)
53  	}
54  	if !strings.Contains(out, "sync: outcome=") {
55  		t.Fatalf("expected `sync: outcome=...` summary on stdout; got %q (stderr=%q)", out, stderr.String())
56  	}
57  }
58  
59  // TestCloudAliasesResolveToTopLevel verifies RegisterAlias wiring: callers
60  // reading `cloud.api_key` / `cloud.endpoint` get the top-level `api_key` /
61  // `endpoint` values. This is Bug 2's regression guard.
62  func TestCloudAliasesResolveToTopLevel(t *testing.T) {
63  	home := t.TempDir()
64  	shannonDir := filepath.Join(home, ".shannon")
65  	if err := os.MkdirAll(shannonDir, 0700); err != nil {
66  		t.Fatalf("mkdir shannon dir: %v", err)
67  	}
68  	cfgYAML := "api_key: top-level-key-value\nendpoint: https://example.test\n"
69  	if err := os.WriteFile(filepath.Join(shannonDir, "config.yaml"), []byte(cfgYAML), 0600); err != nil {
70  		t.Fatalf("write config: %v", err)
71  	}
72  
73  	withIsolatedEnv(t, home)
74  	viper.Reset()
75  
76  	if _, err := config.Load(); err != nil {
77  		t.Fatalf("config.Load: %v", err)
78  	}
79  
80  	if got := viper.GetString("cloud.api_key"); got != "top-level-key-value" {
81  		t.Fatalf("cloud.api_key alias: got %q, want top-level value", got)
82  	}
83  	if got := viper.GetString("cloud.endpoint"); got != "https://example.test" {
84  		t.Fatalf("cloud.endpoint alias: got %q, want top-level value", got)
85  	}
86  }
87  
88  // withIsolatedEnv redirects HOME (and XDG_CONFIG_HOME for good measure) to
89  // the supplied tempdir for the duration of the test. Cleanup restores the
90  // prior values.
91  func withIsolatedEnv(t *testing.T, home string) {
92  	t.Helper()
93  	t.Setenv("HOME", home)
94  	t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
95  }