Skip to content

Commit c3fdf55

Browse files
committed
test: add ~200 unit tests across cli/config/installer/sync; fix browser open in tests
- Add output_test.go (67 tests for cli output helpers) - Add plan_test.go, reporter_test.go, installer_extra_test.go (77 tests) - Add diff_extra_test.go, options_test.go and config extra tests (84 tests) - Stub openBrowser in all recordPublishResult tests to prevent real browser opens during `go test` runs - Overall unit coverage: 57% → 63%
1 parent 140886c commit c3fdf55

11 files changed

Lines changed: 3324 additions & 0 deletions

internal/cli/output_test.go

Lines changed: 906 additions & 0 deletions
Large diffs are not rendered by default.

internal/cli/snapshot_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,16 @@ func TestLoadSnapshot_LocalFile_NotFound(t *testing.T) {
215215

216216
// ── recordPublishResult ───────────────────────────────────────────────────────
217217

218+
func withNoSnapshotBrowser(t *testing.T) {
219+
t.Helper()
220+
orig := openBrowser
221+
openBrowser = func(string) error { return nil }
222+
t.Cleanup(func() { openBrowser = orig })
223+
}
224+
218225
func TestRecordPublishResult_NewConfig_SavesSyncSource(t *testing.T) {
219226
t.Setenv("HOME", t.TempDir())
227+
withNoSnapshotBrowser(t)
220228

221229
// targetSlug="" means first publish → should save sync source.
222230
recordPublishResult("alice", "my-new-config", "", "public", "https://openboot.dev")
@@ -231,6 +239,7 @@ func TestRecordPublishResult_NewConfig_SavesSyncSource(t *testing.T) {
231239

232240
func TestRecordPublishResult_UpdateExisting_DoesNotOverrideSyncSource(t *testing.T) {
233241
t.Setenv("HOME", t.TempDir())
242+
withNoSnapshotBrowser(t)
234243

235244
// Pre-write a sync source for a different config.
236245
writeSyncSourceForSnap(t, "original-slug")
@@ -247,6 +256,7 @@ func TestRecordPublishResult_UpdateExisting_DoesNotOverrideSyncSource(t *testing
247256

248257
func TestRecordPublishResult_EmptyResultSlug_NoSave(t *testing.T) {
249258
t.Setenv("HOME", t.TempDir())
259+
withNoSnapshotBrowser(t)
250260

251261
// resultSlug="" and targetSlug="" — nothing to save.
252262
recordPublishResult("alice", "", "", "private", "https://openboot.dev")

internal/config/options_test.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// ---- ToInstallOptions ----
11+
12+
func TestToInstallOptions_AllFields(t *testing.T) {
13+
rc := &RemoteConfig{Username: "alice", Slug: "setup"}
14+
cfg := &Config{
15+
Version: "1.2.3",
16+
Preset: "developer",
17+
User: "alice",
18+
DryRun: true,
19+
Silent: true,
20+
PackagesOnly: true,
21+
Update: true,
22+
Shell: "install",
23+
Macos: "configure",
24+
Dotfiles: "clone",
25+
GitName: "Alice",
26+
GitEmail: "alice@example.com",
27+
PostInstall: "mise install",
28+
AllowPostInstall: true,
29+
DotfilesURL: "https://github.com/alice/dotfiles",
30+
RemoteConfig: rc,
31+
}
32+
33+
opts := cfg.ToInstallOptions()
34+
require.NotNil(t, opts)
35+
36+
assert.Equal(t, "1.2.3", opts.Version)
37+
assert.Equal(t, "developer", opts.Preset)
38+
assert.Equal(t, "alice", opts.User)
39+
assert.True(t, opts.DryRun)
40+
assert.True(t, opts.Silent)
41+
assert.True(t, opts.PackagesOnly)
42+
assert.True(t, opts.Update)
43+
assert.Equal(t, "install", opts.Shell)
44+
assert.Equal(t, "configure", opts.Macos)
45+
assert.Equal(t, "clone", opts.Dotfiles)
46+
assert.Equal(t, "Alice", opts.GitName)
47+
assert.Equal(t, "alice@example.com", opts.GitEmail)
48+
assert.Equal(t, "mise install", opts.PostInstall)
49+
assert.True(t, opts.AllowPostInstall)
50+
assert.Equal(t, "https://github.com/alice/dotfiles", opts.DotfilesURL)
51+
}
52+
53+
func TestToInstallOptions_ZeroConfig(t *testing.T) {
54+
cfg := &Config{}
55+
opts := cfg.ToInstallOptions()
56+
require.NotNil(t, opts)
57+
58+
assert.Equal(t, "", opts.Version)
59+
assert.Equal(t, "", opts.Preset)
60+
assert.Equal(t, "", opts.User)
61+
assert.False(t, opts.DryRun)
62+
assert.False(t, opts.Silent)
63+
assert.False(t, opts.PackagesOnly)
64+
assert.False(t, opts.Update)
65+
assert.Equal(t, "", opts.Shell)
66+
assert.Equal(t, "", opts.Macos)
67+
assert.Equal(t, "", opts.Dotfiles)
68+
assert.Equal(t, "", opts.GitName)
69+
assert.Equal(t, "", opts.GitEmail)
70+
assert.Equal(t, "", opts.PostInstall)
71+
assert.False(t, opts.AllowPostInstall)
72+
assert.Equal(t, "", opts.DotfilesURL)
73+
}
74+
75+
// ---- ToInstallState ----
76+
77+
func TestToInstallState_AllFields(t *testing.T) {
78+
rc := &RemoteConfig{Username: "bob", Slug: "dev"}
79+
git := &SnapshotGitConfig{UserName: "Bob", UserEmail: "bob@example.com"}
80+
macOSPrefs := []RemoteMacOSPref{
81+
{Domain: "com.apple.dock", Key: "autohide", Type: "bool", Value: "true"},
82+
}
83+
84+
cfg := &Config{
85+
SelectedPkgs: map[string]bool{"git": true, "curl": false},
86+
OnlinePkgs: []Package{{Name: "git", Description: "VCS"}},
87+
SnapshotTaps: []string{"homebrew/core"},
88+
RemoteConfig: rc,
89+
SnapshotGit: git,
90+
SnapshotMacOS: macOSPrefs,
91+
SnapshotDotfiles: "https://github.com/bob/dotfiles",
92+
SnapshotShellOhMyZsh: true,
93+
SnapshotShellTheme: "agnoster",
94+
SnapshotShellPlugins: []string{"git", "z"},
95+
}
96+
97+
state := cfg.ToInstallState()
98+
require.NotNil(t, state)
99+
100+
assert.Equal(t, map[string]bool{"git": true, "curl": false}, state.SelectedPkgs)
101+
assert.Len(t, state.OnlinePkgs, 1)
102+
assert.Equal(t, "git", state.OnlinePkgs[0].Name)
103+
assert.Equal(t, []string{"homebrew/core"}, state.SnapshotTaps)
104+
assert.Equal(t, rc, state.RemoteConfig)
105+
assert.Equal(t, git, state.SnapshotGit)
106+
assert.Equal(t, macOSPrefs, state.SnapshotMacOS)
107+
assert.Equal(t, "https://github.com/bob/dotfiles", state.SnapshotDotfiles)
108+
assert.True(t, state.SnapshotShellOhMyZsh)
109+
assert.Equal(t, "agnoster", state.SnapshotShellTheme)
110+
assert.Equal(t, []string{"git", "z"}, state.SnapshotShellPlugins)
111+
}
112+
113+
func TestToInstallState_ZeroConfig(t *testing.T) {
114+
cfg := &Config{}
115+
state := cfg.ToInstallState()
116+
require.NotNil(t, state)
117+
118+
assert.Nil(t, state.SelectedPkgs)
119+
assert.Nil(t, state.OnlinePkgs)
120+
assert.Nil(t, state.SnapshotTaps)
121+
assert.Nil(t, state.RemoteConfig)
122+
assert.Nil(t, state.SnapshotGit)
123+
assert.Nil(t, state.SnapshotMacOS)
124+
assert.Equal(t, "", state.SnapshotDotfiles)
125+
assert.False(t, state.SnapshotShellOhMyZsh)
126+
assert.Equal(t, "", state.SnapshotShellTheme)
127+
assert.Nil(t, state.SnapshotShellPlugins)
128+
}
129+
130+
// ---- ApplyState ----
131+
132+
func TestApplyState_AllFields(t *testing.T) {
133+
rc := &RemoteConfig{Username: "carol", Slug: "home"}
134+
git := &SnapshotGitConfig{UserName: "Carol", UserEmail: "carol@example.com"}
135+
macOSPrefs := []RemoteMacOSPref{
136+
{Domain: "NSGlobalDomain", Key: "AppleShowScrollBars", Type: "string", Value: "Always"},
137+
}
138+
139+
state := &InstallState{
140+
SelectedPkgs: map[string]bool{"ripgrep": true},
141+
OnlinePkgs: []Package{{Name: "ripgrep", Description: "Search tool"}},
142+
SnapshotTaps: []string{"homebrew/cask"},
143+
RemoteConfig: rc,
144+
SnapshotGit: git,
145+
SnapshotMacOS: macOSPrefs,
146+
SnapshotDotfiles: "https://github.com/carol/dotfiles",
147+
SnapshotShellOhMyZsh: true,
148+
SnapshotShellTheme: "powerlevel10k",
149+
SnapshotShellPlugins: []string{"git", "zsh-autosuggestions"},
150+
}
151+
152+
cfg := &Config{}
153+
cfg.ApplyState(state)
154+
155+
assert.Equal(t, map[string]bool{"ripgrep": true}, cfg.SelectedPkgs)
156+
assert.Len(t, cfg.OnlinePkgs, 1)
157+
assert.Equal(t, "ripgrep", cfg.OnlinePkgs[0].Name)
158+
assert.Equal(t, []string{"homebrew/cask"}, cfg.SnapshotTaps)
159+
assert.Equal(t, rc, cfg.RemoteConfig)
160+
assert.Equal(t, git, cfg.SnapshotGit)
161+
assert.Equal(t, macOSPrefs, cfg.SnapshotMacOS)
162+
assert.Equal(t, "https://github.com/carol/dotfiles", cfg.SnapshotDotfiles)
163+
assert.True(t, cfg.SnapshotShellOhMyZsh)
164+
assert.Equal(t, "powerlevel10k", cfg.SnapshotShellTheme)
165+
assert.Equal(t, []string{"git", "zsh-autosuggestions"}, cfg.SnapshotShellPlugins)
166+
}
167+
168+
func TestApplyState_ZeroState(t *testing.T) {
169+
cfg := &Config{
170+
SelectedPkgs: map[string]bool{"old": true},
171+
SnapshotDotfiles: "https://github.com/old/dotfiles",
172+
SnapshotShellOhMyZsh: true,
173+
}
174+
175+
state := &InstallState{}
176+
cfg.ApplyState(state)
177+
178+
// All fields should be overwritten with zero values from state.
179+
assert.Nil(t, cfg.SelectedPkgs)
180+
assert.Equal(t, "", cfg.SnapshotDotfiles)
181+
assert.False(t, cfg.SnapshotShellOhMyZsh)
182+
}
183+
184+
// ---- Round-trip: ToInstallState → ApplyState ----
185+
186+
func TestInstallState_RoundTrip(t *testing.T) {
187+
rc := &RemoteConfig{Username: "eve", Slug: "workstation"}
188+
original := &Config{
189+
SelectedPkgs: map[string]bool{"fd": true, "bat": true},
190+
OnlinePkgs: []Package{{Name: "fd"}, {Name: "bat"}},
191+
SnapshotTaps: []string{"homebrew/core", "homebrew/cask"},
192+
RemoteConfig: rc,
193+
SnapshotDotfiles: "https://github.com/eve/dots",
194+
SnapshotShellOhMyZsh: true,
195+
SnapshotShellTheme: "robbyrussell",
196+
SnapshotShellPlugins: []string{"git"},
197+
}
198+
199+
state := original.ToInstallState()
200+
201+
restored := &Config{}
202+
restored.ApplyState(state)
203+
204+
assert.Equal(t, original.SelectedPkgs, restored.SelectedPkgs)
205+
assert.Equal(t, original.OnlinePkgs, restored.OnlinePkgs)
206+
assert.Equal(t, original.SnapshotTaps, restored.SnapshotTaps)
207+
assert.Equal(t, original.RemoteConfig, restored.RemoteConfig)
208+
assert.Equal(t, original.SnapshotDotfiles, restored.SnapshotDotfiles)
209+
assert.Equal(t, original.SnapshotShellOhMyZsh, restored.SnapshotShellOhMyZsh)
210+
assert.Equal(t, original.SnapshotShellTheme, restored.SnapshotShellTheme)
211+
assert.Equal(t, original.SnapshotShellPlugins, restored.SnapshotShellPlugins)
212+
}
213+
214+
// ToInstallOptions does NOT include state fields (runtime-only fields).
215+
func TestToInstallOptions_DoesNotLeakStateFields(t *testing.T) {
216+
cfg := &Config{
217+
SelectedPkgs: map[string]bool{"git": true},
218+
OnlinePkgs: []Package{{Name: "git"}},
219+
}
220+
opts := cfg.ToInstallOptions()
221+
// InstallOptions has no SelectedPkgs or OnlinePkgs — simply confirm it compiles
222+
// and returns a valid struct with the correct type.
223+
require.NotNil(t, opts)
224+
assert.IsType(t, &InstallOptions{}, opts)
225+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// ---- GetCategories ----
11+
12+
func TestGetCategories_ReturnsNonEmpty(t *testing.T) {
13+
cats := GetCategories()
14+
assert.NotEmpty(t, cats, "GetCategories must return at least one category from the embedded YAML")
15+
}
16+
17+
func TestGetCategories_ContainsPackages(t *testing.T) {
18+
cats := GetCategories()
19+
total := 0
20+
for _, cat := range cats {
21+
total += len(cat.Packages)
22+
}
23+
assert.Greater(t, total, 0, "expected packages inside categories")
24+
}
25+
26+
func TestGetCategories_ReturnsCopy(t *testing.T) {
27+
// Mutating the returned slice must not affect a subsequent call.
28+
cats1 := GetCategories()
29+
require.NotEmpty(t, cats1)
30+
cats1[0].Name = "MUTATED"
31+
32+
cats2 := GetCategories()
33+
require.NotEmpty(t, cats2)
34+
assert.NotEqual(t, "MUTATED", cats2[0].Name, "GetCategories must return a deep copy")
35+
}
36+
37+
func TestGetCategories_EachCategoryHasName(t *testing.T) {
38+
for _, cat := range GetCategories() {
39+
assert.NotEmpty(t, cat.Name, "every category must have a non-empty name")
40+
}
41+
}
42+
43+
// ---- GetAllPackageNames ----
44+
45+
func TestGetAllPackageNames_ReturnsNonEmpty(t *testing.T) {
46+
names := GetAllPackageNames()
47+
assert.NotEmpty(t, names)
48+
}
49+
50+
func TestGetAllPackageNames_ContainsKnownPackages(t *testing.T) {
51+
names := GetAllPackageNames()
52+
nameSet := make(map[string]bool, len(names))
53+
for _, n := range names {
54+
nameSet[n] = true
55+
}
56+
// curl and wget are in the Essential category of the embedded YAML.
57+
assert.True(t, nameSet["curl"], "curl should be in the package catalog")
58+
assert.True(t, nameSet["wget"], "wget should be in the package catalog")
59+
}
60+
61+
func TestGetAllPackageNames_NoDuplicates(t *testing.T) {
62+
names := GetAllPackageNames()
63+
seen := make(map[string]int)
64+
for _, n := range names {
65+
seen[n]++
66+
}
67+
for name, count := range seen {
68+
assert.Equal(t, 1, count, "package %q appears %d times; expected exactly once", name, count)
69+
}
70+
}
71+
72+
// ---- CatalogDescriptionMap ----
73+
74+
func TestCatalogDescriptionMap_ReturnsNonNil(t *testing.T) {
75+
m := CatalogDescriptionMap()
76+
assert.NotNil(t, m)
77+
}
78+
79+
func TestCatalogDescriptionMap_ContainsDescriptions(t *testing.T) {
80+
m := CatalogDescriptionMap()
81+
// The embedded YAML has descriptions for many packages. At least one should exist.
82+
assert.Greater(t, len(m), 0, "CatalogDescriptionMap must return at least one entry")
83+
}
84+
85+
func TestCatalogDescriptionMap_ValuesAreNonEmpty(t *testing.T) {
86+
m := CatalogDescriptionMap()
87+
for name, desc := range m {
88+
assert.NotEmpty(t, desc, "description for %q should not be empty", name)
89+
}
90+
}
91+
92+
func TestCatalogDescriptionMap_ReturnsCopy(t *testing.T) {
93+
// Mutating the returned map must not affect a subsequent call.
94+
m1 := CatalogDescriptionMap()
95+
for k := range m1 {
96+
m1[k] = "MUTATED"
97+
break
98+
}
99+
m2 := CatalogDescriptionMap()
100+
for _, v := range m2 {
101+
assert.NotEqual(t, "MUTATED", v)
102+
}
103+
}

0 commit comments

Comments
 (0)