package ui import ( "context" "strings" "testing" ) // stubSink is the smallest Sink that can carry a working directory. type stubSink struct{ cwd string } func (stubSink) Text(string) {} func (stubSink) ToolStart(ToolEvent) {} func (stubSink) Allow(context.Context, ToolEvent) bool { return true } func (stubSink) ToolRunning(string) {} func (stubSink) ToolEnd(string, ToolResult) {} func (s stubSink) WorkDir() string { return s.cwd } // With no sink — the terminal front end — Resolve must be the identity: part // 10's behaviour, paths relative to the process, unchanged. func TestResolveWithoutSinkIsIdentity(t *testing.T) { SetActive(nil) for _, p := range []string{"hello/main.go", "../x", "/abs/path"} { if got := Resolve(p); got != p { t.Errorf("Resolve(%q) = %q, want unchanged", p, got) } } } // With a sink, a relative path follows the SESSION's working directory — the // project open in the editor — and an absolute one is left alone. func TestResolveWithSinkFollowsTheSession(t *testing.T) { SetActive(stubSink{cwd: "/work/project"}) defer SetActive(nil) if got := Resolve("hello/main.go"); got != "/work/project/hello/main.go" { t.Errorf("relative path: got %q", got) } if got := Resolve("/etc/hosts"); got != "/etc/hosts" { t.Errorf("absolute path must not move: got %q", got) } } // Call IDs must never repeat: an ACP client correlates tool_call and // tool_call_update by them, and a duplicate would merge two calls in its UI. func TestNextCallIDNeverRepeats(t *testing.T) { a, b := NextCallID(), NextCallID() if a == b { t.Fatalf("two calls, same id: %q", a) } if !strings.HasPrefix(a, "call_") { t.Fatalf("unexpected shape: %q", a) } }