summaryrefslogtreecommitdiff
path: root/internal/aitool/dispatch_test.go
blob: 6842668c8e5644c2b56c42fcb48f05f5e0c2d025 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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)
	}
}