Skip to content
This repository was archived by the owner on May 23, 2026. It is now read-only.

Commit bbbb99d

Browse files
committed
test(install): add integration tests for install/uninstall/completion
9 integration tests: completion (bash/zsh/fish/invalid), install (fresh + idempotent), uninstall (clean/edited/force), clean-cutover verification. Install tests skip gracefully when opencode binary unavailable.
1 parent 97e7c47 commit bbbb99d

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

tests/install_integration_test.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package tests
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
9+
"github.com/Sharper-Flow/Opencode-Advance/internal/install"
10+
)
11+
12+
// --- Completion integration tests ---
13+
14+
func TestCompletionBashIntegration(t *testing.T) {
15+
root := repoRoot(t)
16+
stdout, _, err := runOCA(t, root, nil, "completion", "bash")
17+
if err != nil {
18+
t.Fatalf("completion bash: %v", err)
19+
}
20+
if !strings.Contains(stdout, "# bash completion") {
21+
t.Error("bash completion output should contain completion header")
22+
}
23+
if !strings.Contains(stdout, "oca") {
24+
t.Error("bash completion output should reference oca")
25+
}
26+
}
27+
28+
func TestCompletionZshIntegration(t *testing.T) {
29+
root := repoRoot(t)
30+
stdout, _, err := runOCA(t, root, nil, "completion", "zsh")
31+
if err != nil {
32+
t.Fatalf("completion zsh: %v", err)
33+
}
34+
if !strings.Contains(stdout, "#compdef oca") {
35+
t.Error("zsh completion output should contain #compdef directive")
36+
}
37+
}
38+
39+
func TestCompletionFishIntegration(t *testing.T) {
40+
root := repoRoot(t)
41+
_, stderr, err := runOCA(t, root, nil, "completion", "fish")
42+
if err == nil {
43+
t.Fatal("fish completion should fail in v1")
44+
}
45+
if !strings.Contains(stderr, "fish") || !strings.Contains(stderr, "v1.1") {
46+
t.Errorf("fish error should mention v1.1, got stderr: %s", stderr)
47+
}
48+
}
49+
50+
func TestCompletionInvalidShellIntegration(t *testing.T) {
51+
root := repoRoot(t)
52+
_, stderr, err := runOCA(t, root, nil, "completion", "powershell")
53+
if err == nil {
54+
t.Fatal("invalid shell should fail")
55+
}
56+
if !strings.Contains(stderr, "unsupported") {
57+
t.Errorf("error should mention unsupported shell, got: %s", stderr)
58+
}
59+
}
60+
61+
// --- Install/Uninstall integration tests ---
62+
63+
// isolatedHomeEnv returns env vars that redirect HOME and XDG dirs to a temp dir.
64+
func isolatedHomeEnv(t *testing.T) ([]string, string) {
65+
t.Helper()
66+
home := t.TempDir()
67+
return []string{
68+
"HOME=" + home,
69+
"XDG_CONFIG_HOME=" + filepath.Join(home, ".config"),
70+
"XDG_DATA_HOME=" + filepath.Join(home, ".local", "share"),
71+
"XDG_RUNTIME_DIR=" + filepath.Join(home, ".run"),
72+
"XDG_STATE_HOME=" + filepath.Join(home, ".local", "state"),
73+
}, home
74+
}
75+
76+
func TestInstallCreatesDirsAndRCFiles(t *testing.T) {
77+
root := repoRoot(t)
78+
env, home := isolatedHomeEnv(t)
79+
80+
// Prereq check needs git/tmux/opencode — if any are missing, skip
81+
if _, err := os.Stat(filepath.Join(home, ".bashrc")); !os.IsNotExist(err) {
82+
t.Fatal("test isolation: .bashrc should not exist before install")
83+
}
84+
85+
stdout, stderr, err := runOCA(t, root, env, "install")
86+
if err != nil {
87+
// May fail if prerequisites are missing on this machine
88+
t.Logf("install failed (may be prereq issue): %s\nstderr: %s", err, stderr)
89+
t.Skip("skipping: install prerequisites not met on this machine")
90+
}
91+
92+
// Check dirs created
93+
for _, dir := range []string{
94+
filepath.Join(home, ".config", "opencode"),
95+
filepath.Join(home, ".config", "vision"),
96+
} {
97+
if _, err := os.Stat(dir); os.IsNotExist(err) {
98+
t.Errorf("expected dir %s to exist after install", dir)
99+
}
100+
}
101+
102+
// Check rc files have managed blocks
103+
for _, rc := range []string{".bashrc", ".zshrc"} {
104+
path := filepath.Join(home, rc)
105+
data, err := os.ReadFile(path)
106+
if err != nil {
107+
t.Errorf("expected %s to exist after install", path)
108+
continue
109+
}
110+
content := string(data)
111+
if !strings.Contains(content, install.SentinelStart) {
112+
t.Errorf("%s should contain start sentinel", rc)
113+
}
114+
if !strings.Contains(content, install.SentinelEnd) {
115+
t.Errorf("%s should contain end sentinel", rc)
116+
}
117+
}
118+
119+
_ = stdout // stdout contains summary text
120+
}
121+
122+
func TestIdempotentInstall(t *testing.T) {
123+
root := repoRoot(t)
124+
env, home := isolatedHomeEnv(t)
125+
126+
// First install
127+
_, stderr1, err1 := runOCA(t, root, env, "install")
128+
if err1 != nil {
129+
t.Logf("first install failed: %s\nstderr: %s", err1, stderr1)
130+
t.Skip("skipping: install prerequisites not met")
131+
}
132+
133+
bashrc := filepath.Join(home, ".bashrc")
134+
first, _ := os.ReadFile(bashrc)
135+
136+
// Second install
137+
_, stderr2, err2 := runOCA(t, root, env, "install")
138+
if err2 != nil {
139+
t.Fatalf("second install failed: %s\nstderr: %s", err2, stderr2)
140+
}
141+
142+
second, _ := os.ReadFile(bashrc)
143+
144+
if string(first) != string(second) {
145+
t.Error("idempotent install should produce identical rc file")
146+
}
147+
}
148+
149+
func TestUninstallRemovesBlocks(t *testing.T) {
150+
root := repoRoot(t)
151+
env, home := isolatedHomeEnv(t)
152+
153+
// Install first
154+
_, stderr, err := runOCA(t, root, env, "install")
155+
if err != nil {
156+
t.Logf("install failed: %s\nstderr: %s", err, stderr)
157+
t.Skip("skipping: install prerequisites not met")
158+
}
159+
160+
// Now uninstall
161+
_, stderr, err = runOCA(t, root, env, "uninstall")
162+
if err != nil {
163+
t.Fatalf("uninstall failed: %s\nstderr: %s", err, stderr)
164+
}
165+
166+
// Verify blocks removed
167+
for _, rc := range []string{".bashrc", ".zshrc"} {
168+
data, _ := os.ReadFile(filepath.Join(home, rc))
169+
if strings.Contains(string(data), install.SentinelStart) {
170+
t.Errorf("%s should not contain managed block after uninstall", rc)
171+
}
172+
}
173+
}
174+
175+
func TestUninstallEditedBlockFails(t *testing.T) {
176+
root := repoRoot(t)
177+
env, home := isolatedHomeEnv(t)
178+
179+
// Install first
180+
_, stderr, err := runOCA(t, root, env, "install")
181+
if err != nil {
182+
t.Logf("install failed: %s\nstderr: %s", err, stderr)
183+
t.Skip("skipping: install prerequisites not met")
184+
}
185+
186+
// Edit the managed block in .bashrc
187+
bashrc := filepath.Join(home, ".bashrc")
188+
data, _ := os.ReadFile(bashrc)
189+
edited := strings.ReplaceAll(string(data), "unset _oca_bindir", "unset _oca_bindir\n# USER EDIT")
190+
os.WriteFile(bashrc, []byte(edited), 0o644)
191+
192+
// Uninstall should fail
193+
_, stderr, err = runOCA(t, root, env, "uninstall")
194+
if err == nil {
195+
t.Fatal("uninstall should fail when block is edited")
196+
}
197+
if !strings.Contains(stderr, "edited") && !strings.Contains(stderr, "--force") {
198+
t.Errorf("error should mention edited block and --force, got: %s", stderr)
199+
}
200+
}
201+
202+
func TestUninstallForceOverridesEditedBlock(t *testing.T) {
203+
root := repoRoot(t)
204+
env, home := isolatedHomeEnv(t)
205+
206+
// Install first
207+
_, stderr, err := runOCA(t, root, env, "install")
208+
if err != nil {
209+
t.Logf("install failed: %s\nstderr: %s", err, stderr)
210+
t.Skip("skipping: install prerequisites not met")
211+
}
212+
213+
// Edit the managed block
214+
bashrc := filepath.Join(home, ".bashrc")
215+
data, _ := os.ReadFile(bashrc)
216+
edited := strings.ReplaceAll(string(data), "unset _oca_bindir", "unset _oca_bindir\n# USER EDIT")
217+
os.WriteFile(bashrc, []byte(edited), 0o644)
218+
219+
// Uninstall with --force
220+
_, stderr, err = runOCA(t, root, env, "uninstall", "--force")
221+
if err != nil {
222+
t.Fatalf("uninstall --force should succeed, got: %s\nstderr: %s", err, stderr)
223+
}
224+
225+
// Verify blocks removed
226+
data, _ = os.ReadFile(bashrc)
227+
if strings.Contains(string(data), install.SentinelStart) {
228+
t.Error(".bashrc should not contain managed block after force uninstall")
229+
}
230+
}
231+
232+
func TestCleanCutoverNoRealConfigWrites(t *testing.T) {
233+
root := repoRoot(t)
234+
env, _ := isolatedHomeEnv(t)
235+
236+
// Record real home dir state
237+
realHome, _ := os.UserHomeDir()
238+
realBashrc := filepath.Join(realHome, ".bashrc")
239+
before, err := os.ReadFile(realBashrc)
240+
if err != nil {
241+
t.Skip("no real .bashrc to protect")
242+
}
243+
244+
// Run install with isolated env
245+
_, _, _ = runOCA(t, root, env, "install")
246+
247+
// Verify real .bashrc unchanged
248+
after, _ := os.ReadFile(realBashrc)
249+
if string(before) != string(after) {
250+
t.Error("clean-cutover violation: real .bashrc was modified during test")
251+
}
252+
}

0 commit comments

Comments
 (0)