summaryrefslogtreecommitdiff
path: root/internal/aitool/dispatch_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/aitool/dispatch_test.go')
-rw-r--r--internal/aitool/dispatch_test.go113
1 files changed, 113 insertions, 0 deletions
diff --git a/internal/aitool/dispatch_test.go b/internal/aitool/dispatch_test.go
new file mode 100644
index 0000000..6842668
--- /dev/null
+++ b/internal/aitool/dispatch_test.go
@@ -0,0 +1,113 @@
+package aitool
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func TestChain(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ preferred string
+ want []Tool
+ }{
+ {
+ name: "default chain when empty",
+ want: []Tool{ToolOpencode, ToolHexAI, ToolClaude, ToolAmp},
+ },
+ {
+ name: "default chain when opencode",
+ preferred: "opencode",
+ want: []Tool{ToolOpencode, ToolHexAI, ToolClaude, ToolAmp},
+ },
+ {
+ name: "hexai chain",
+ preferred: "hexai",
+ want: []Tool{ToolHexAI, ToolClaude, ToolAmp},
+ },
+ {
+ name: "claude alias chain",
+ preferred: "claude-code",
+ want: []Tool{ToolClaude, ToolAmp},
+ },
+ {
+ name: "amp only",
+ preferred: "amp",
+ want: []Tool{ToolAmp},
+ },
+ {
+ name: "unknown tool",
+ preferred: "unknown",
+ want: nil,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ got := Chain(tt.preferred)
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Fatalf("Chain(%q) = %#v, want %#v", tt.preferred, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestFirstAvailable(t *testing.T) {
+ t.Parallel()
+
+ lookPath := fakeLookPath("claude", "amp")
+ got := FirstAvailable("", lookPath)
+ if got != ToolClaude {
+ t.Fatalf("FirstAvailable() = %q, want %q", got, ToolClaude)
+ }
+}
+
+func TestFirstAvailable_NoToolsFound(t *testing.T) {
+ t.Parallel()
+
+ got := FirstAvailable("", fakeLookPath())
+ if got != "" {
+ t.Fatalf("FirstAvailable() = %q, want empty", got)
+ }
+}
+
+func TestIsAvailable_OpencodeUsesOllamaBinary(t *testing.T) {
+ t.Parallel()
+
+ if !IsAvailable(ToolOpencode, fakeLookPath("ollama")) {
+ t.Fatal("expected opencode to be available when ollama exists")
+ }
+
+ if IsAvailable(ToolOpencode, fakeLookPath("opencode")) {
+ t.Fatal("expected opencode to be unavailable when only opencode binary exists")
+ }
+}
+
+func TestAvailableChain_FiltersUnavailableTools(t *testing.T) {
+ t.Parallel()
+
+ got := AvailableChain("", fakeLookPath("hexai", "amp"))
+ want := []Tool{ToolHexAI, ToolAmp}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("AvailableChain() = %#v, want %#v", got, want)
+ }
+}
+
+func fakeLookPath(tools ...string) LookPathFunc {
+ available := make(map[string]struct{}, len(tools))
+ for _, tool := range tools {
+ available[tool] = struct{}{}
+ }
+
+ return func(file string) (string, error) {
+ if _, ok := available[file]; ok {
+ return "/usr/bin/" + file, nil
+ }
+ return "", fmt.Errorf("%s not found", file)
+ }
+}