package adminapi import ( "context" "log/slog" "net/http" "testing" "gitea.mynodes.xyz/ilker/llmgw/internal/control" "gitea.mynodes.xyz/ilker/llmgw/internal/control/inventory" ) // choiceStore keeps engine choices in memory. type choiceStore struct{ m map[string]map[string]string } func (c *choiceStore) NodeEngines(context.Context) (map[string]map[string]string, error) { return c.m, nil } func (c *choiceStore) SetNodeEngine(_ context.Context, node, engine, variant, _ string) error { if c.m[node] == nil { c.m[node] = map[string]string{} } c.m[node][engine] = variant return nil } func TestAnEngineBuildIsChosenForANodeAndTheNodeIsTold(t *testing.T) { a, ctl := setup(t, testModels(), gpuNode("gpu07", 8), inventory.Node{ID: "cpu01", State: inventory.Ready}) ch := control.NewEngineChoices(t.Context(), &choiceStore{m: map[string]map[string]string{}}, slog.New(slog.DiscardHandler)) var told []string ch.OnChange(func(node string) error { told = append(told, node); return nil }) ctl.SetEngineChoices(ch) if code := call(t, a, http.MethodPost, "/api/v1/nodes/gpu07/engines", `{"engine":"vllm","build":"cu129"}`, nil); code != http.StatusOK { t.Fatalf("status %d", code) } if len(told) != 1 || told[0] != "gpu07" { t.Errorf("the node was not told: %v", told) } var nodes []NodeView call(t, a, http.MethodGet, "/api/v1/nodes", "", &nodes) for _, n := range nodes { switch n.ID { case "gpu07": if n.EngineBuilds["vllm"] != "cu129" { t.Errorf("gpu07 shows %v", n.EngineBuilds) } case "cpu01": if n.EngineBuilds != nil { t.Errorf("a node with no GPU has no build to choose: %v", n.EngineBuilds) } } } for _, bad := range []string{`{"engine":"vllm","build":"cu200"}`, `{"engine":"llamacpp","build":"cu129"}`} { if code := call(t, a, http.MethodPost, "/api/v1/nodes/gpu07/engines", bad, nil); code != http.StatusBadRequest { t.Errorf("%s: status %d", bad, code) } } if code := call(t, a, http.MethodPost, "/api/v1/nodes/gpu07/engines", `{"engine":"vllm","build":"auto"}`, nil); code != http.StatusOK || ch.For("gpu07")["vllm"] != "" { t.Errorf("auto should leave it to the node again: %d %v", code, ch.For("gpu07")) } }