Files
llmgw/internal/cli/model_presets_test.go
T
Ilker ManapandClaude Opus 5.5 848db08469
CI / test (push) Failing after 6m27s
The cluster page and the console say which picture and 3D models are here, and add them
A site whose catalog had no flux-schnell or triposr showed no Images or 3D
page, and nothing said they were one click away. The cluster page now has
a Pictures and 3D models card under the models: each preset, what it
makes, the classes of card here it fits, whether and by whom it was
added, and why the leader has not added it by itself; an Add button, which
says first when it would install ComfyUI on the GPU nodes and restart the
control planes; and the switch for presets.auto. The console's models
screen shows the same under its table: A adds one, asking first in the
same case, and P turns automatic adding on or off.

`ctl model add PRESET` and `ctl model presets` go through the presets API,
so the control plane keeps who added one, and `ctl model presets auto
on|off` flips the setting; against a control plane from before, add sends
the entry as v0.14.4 did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:48:40 +02:00

129 lines
4.3 KiB
Go

package cli
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
)
// presetAdmin is an admin API holding settings, which records the models
// added and the settings put.
type presetAdmin struct {
// api is a control plane that adds presets itself; without it, one
// from before, which answers their paths with a 404.
api bool
byAPI []string
mu sync.Mutex
settings string
added []catalog.Model
put []string
}
func (a *presetAdmin) client(t *testing.T) func() *ctlClient {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
a.mu.Lock()
defer a.mu.Unlock()
body, _ := io.ReadAll(r.Body)
w.Header().Set("Content-Type", "application/json")
switch {
case a.api && r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/api/v1/presets/"):
name := strings.TrimPrefix(r.URL.Path, "/api/v1/presets/")
a.byAPI = append(a.byAPI, name)
_ = json.NewEncoder(w).Encode(adminapi.ModelAdded{Added: name})
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/models":
var m catalog.Model
_ = json.Unmarshal(body, &m)
a.added = append(a.added, m)
_ = json.NewEncoder(w).Encode(map[string]string{"name": m.Name})
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/settings":
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 1, Body: a.settings})
case r.Method == http.MethodPut && r.URL.Path == "/api/v1/settings":
var req adminapi.SettingsRequest
_ = json.Unmarshal(body, &req)
a.put = append(a.put, req.Body)
a.settings = req.Body
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 2, Body: req.Body})
default:
http.NotFound(w, r)
}
}))
t.Cleanup(srv.Close)
return func() *ctlClient { return newCtlClient(strings.TrimPrefix(srv.URL, "http://"), "") }
}
func runModelAdd(t *testing.T, client func() *ctlClient, args ...string) (string, error) {
t.Helper()
cmd := newModelAddCmd(client)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&out)
cmd.SetArgs(append(args, "--no-source-check"))
err := cmd.ExecuteContext(t.Context())
return out.String(), err
}
func TestAPresetIsAddedByTheControlPlane(t *testing.T) {
a := &presetAdmin{api: true}
out, err := runModelAdd(t, a.client(t), "triposr")
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if len(a.byAPI) != 1 || a.byAPI[0] != "triposr" || len(a.added) != 0 || len(a.put) != 0 {
t.Errorf("by the API %v; sent the entry %v; settings %v", a.byAPI, a.added, a.put)
}
}
// A control plane from before presets were its own is sent the entry, and
// ComfyUI set beside it, as the release that brought presets did.
func TestAPresetIsAddedByNameAndHasTheNodesInstallComfyUI(t *testing.T) {
a := &presetAdmin{settings: "router:\n retries: 2\n"}
out, err := runModelAdd(t, a.client(t), "triposr")
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if len(a.added) != 1 || a.added[0].Name != "triposr" || a.added[0].ComfyUI.Workflow != "triposr" {
t.Fatalf("added %+v", a.added)
}
if len(a.put) != 1 || !strings.Contains(a.put[0], "comfyui: 0.39.2") || !strings.Contains(a.put[0], "retries: 2") {
t.Fatalf("settings put %q", a.put)
}
if !strings.Contains(out, "install ComfyUI 0.39.2") {
t.Errorf("not said:\n%s", out)
}
}
func TestAClusterThatNamesItsComfyUIKeepsIt(t *testing.T) {
for _, settings := range []string{"engines:\n comfyui: 0.38.0\n", "engines:\n comfyui: \"\"\n"} {
a := &presetAdmin{settings: settings}
out, err := runModelAdd(t, a.client(t), "flux-schnell")
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if len(a.put) != 0 {
t.Errorf("%q: settings changed to %q", settings, a.put)
}
if none := strings.Contains(settings, `""`); none != strings.Contains(out, "set to none") {
t.Errorf("%q: %s", settings, out)
}
}
}
func TestAnUnknownPresetNamesTheOnesThereAre(t *testing.T) {
a := &presetAdmin{}
_, err := runModelAdd(t, a.client(t), "sdxl")
if err == nil || !strings.Contains(err.Error(), "flux-schnell, triposr") || len(a.added) != 0 {
t.Errorf("%v, added %v", err, a.added)
}
if _, err := runModelAdd(t, a.client(t), "triposr", "-f", "x.yaml"); err == nil {
t.Error("a preset and a file were both taken")
}
}