1package modes
2
3import (
4 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/patriceckhart/zot/packages/provider"
12 "github.com/patriceckhart/zot/packages/tui"
13)
14
15func TestLlamaDialogSearchesAsUserTypes(t *testing.T) {
16 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 if r.URL.Path != "/api/models" || r.URL.Query().Get("search") != "qw" {
18 http.Error(w, "unexpected request", http.StatusBadRequest)
19 return
20 }
21 fmt.Fprint(w, `[{"id":"owner/qwen-GGUF","downloads":42}]`)
22 }))
23 defer server.Close()
24
25 hf := provider.NewHuggingFaceClient("")
26 hf.BaseURL = server.URL
27 updated := make(chan struct{}, 1)
28 d := newLlamaDialog()
29 d.step = llamaSearch
30 d.hf = hf
31 d.invalidate = func() {
32 select {
33 case updated <- struct{}{}:
34 default:
35 }
36 }
37 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'q'})
38 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'w'})
39
40 select {
41 case <-updated:
42 case <-time.After(2 * time.Second):
43 t.Fatal("timed out waiting for live search")
44 }
45 d.mu.Lock()
46 defer d.mu.Unlock()
47 if len(d.results) != 1 || d.results[0].ID != "owner/qwen-GGUF" {
48 t.Fatalf("results = %#v", d.results)
49 }
50 if d.message != "" {
51 t.Fatalf("message = %q", d.message)
52 }
53}
54
55func TestLlamaDialogSearchAcceptsPastedText(t *testing.T) {
56 d := newLlamaDialog()
57 d.step = llamaSearch
58
59 d.HandleKey(tui.Key{Kind: tui.KeyPaste, Paste: "unsloth/Qwen3.5-4B-GGUF\n"})
60 defer d.Close()
61
62 if d.query != "unsloth/Qwen3.5-4B-GGUF" {
63 t.Fatalf("query = %q", d.query)
64 }
65}
66
67func TestLlamaDialogSearchCursorTracksQuery(t *testing.T) {
68 d := newLlamaDialog()
69 d.step = llamaSearch
70 d.query = "qwen"
71 row, col := d.CursorPos()
72 if row != 5 || col != 6 {
73 t.Fatalf("cursor = (%d, %d)", row, col)
74 }
75 d.message = "searching Hugging Face..."
76 row, _ = d.CursorPos()
77 if row != 6 {
78 t.Fatalf("cursor row with status = %d", row)
79 }
80}
81
82func TestLlamaDialogLoadedModelRequiresUnloadConfirmation(t *testing.T) {
83 d := newLlamaDialog()
84 var loaded provider.LlamaCPPModel
85 loaded.ID = "local-model"
86 loaded.Status.Value = "loaded"
87 d.step = llamaModels
88 d.models = []provider.LlamaCPPModel{loaded}
89
90 d.HandleKey(tui.Key{Kind: tui.KeyEnter})
91 if d.step != llamaConfirmUnload || d.selected != "local-model" {
92 t.Fatalf("step = %v, selected = %q", d.step, d.selected)
93 }
94 lines := d.Render(tui.Theme{}, 80)
95 if !strings.Contains(strings.Join(lines, "\n"), "Unload local-model?") {
96 t.Fatalf("render = %q", lines)
97 }
98}
99
100func TestLlamaDialogRemovableModelRequiresDiskRemovalConfirmation(t *testing.T) {
101 d := newLlamaDialog()
102 var cached provider.LlamaCPPModel
103 cached.ID = "owner/model:Q4_K_M"
104 cached.Status.Value = "unloaded"
105 cached.CanRemove = true
106 d.step = llamaModels
107 d.models = []provider.LlamaCPPModel{cached}
108
109 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'd'})
110 if d.step != llamaConfirmRemove || d.selected != cached.ID {
111 t.Fatalf("step = %v, selected = %q", d.step, d.selected)
112 }
113 lines := d.Render(tui.Theme{}, 80)
114 if !strings.Contains(strings.Join(lines, "\n"), "Remove cached model "+cached.ID+"?") {
115 t.Fatalf("render = %q", lines)
116 }
117
118 d.HandleKey(tui.Key{Kind: tui.KeyEsc})
119 if d.step != llamaModels {
120 t.Fatalf("step after escape = %v", d.step)
121 }
122}
123
124func TestLlamaDialogDoesNotRemoveModelsDirEntry(t *testing.T) {
125 d := newLlamaDialog()
126 var local provider.LlamaCPPModel
127 local.ID = "local-model"
128 local.Status.Value = "unloaded"
129 d.step = llamaModels
130 d.models = []provider.LlamaCPPModel{local}
131
132 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'd'})
133 if d.step != llamaModels {
134 t.Fatalf("step = %v", d.step)
135 }
136 if !strings.Contains(d.message, "only router-downloaded cache models") {
137 t.Fatalf("message = %q", d.message)
138 }
139}
140
141func TestLlamaDialogDownloadSearchReturnsToModels(t *testing.T) {
142 d := newLlamaDialog()
143 d.step = llamaModels
144 d.HandleKey(tui.Key{Kind: tui.KeyEnter})
145 if d.step != llamaSearch {
146 t.Fatalf("step = %v", d.step)
147 }
148 d.HandleKey(tui.Key{Kind: tui.KeyEsc})
149 if d.step != llamaModels {
150 t.Fatalf("step after escape = %v", d.step)
151 }
152}