Files
llmgw/internal/cli/service_test.go
T
Ilker ManapandClaude Opus 5.5 63f7c8cfd7 llmgw start --wait waits for the models, not for the setting
Every model of a stopped system asks for no replica and so has what it asks
for. Until the control plane took the setting up, seconds later, start
--wait saw 18 of 18 satisfied and returned at once, with nothing started.
It now counts only once the control plane says it is in the state asked
for, both ways.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 21:43:45 +02:00

138 lines
4.3 KiB
Go

package cli
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
)
func TestTheServicesHereAreFoundAndAskedAbout(t *testing.T) {
old, oldRun := unitDir, serviceRunner
unitDir = t.TempDir()
t.Cleanup(func() { unitDir, serviceRunner = old, oldRun })
for _, u := range []string{"llmgw-server", "llmgw-agent"} {
_ = os.WriteFile(filepath.Join(unitDir, u+".service"), []byte("[Unit]"), 0o644)
}
var asked []string
serviceRunner = func(_ context.Context, args ...string) (string, error) {
asked = append(asked, strings.Join(args, " "))
if args[0] == "is-active" {
return "active", nil
}
return "Wed 2026-10-07 12:00:00 UTC", nil
}
units, err := installedServices(context.Background(), nil)
if err != nil || strings.Join(units, ",") != "llmgw-server,llmgw-agent" {
t.Fatalf("%v %v", units, err)
}
if _, err := installedServices(context.Background(), []string{"mirror"}); err == nil ||
!strings.Contains(err.Error(), "server, agent") {
t.Errorf("a service not here: %v", err)
}
if u, err := installedServices(context.Background(), []string{"agent"}); err != nil || len(u) != 1 {
t.Errorf("one named: %v %v", u, err)
}
if runtime.GOOS != "linux" {
return
}
var out bytes.Buffer
c := serviceVerbs()[0]
c.SetOut(&out)
c.SetContext(context.Background())
if err := c.RunE(c, nil); err != nil {
t.Fatal(err)
}
if !strings.Contains(out.String(), "server") || !strings.Contains(out.String(), "active") {
t.Errorf("%s", out.String())
}
}
func TestStopSetsTheClustersServingStopped(t *testing.T) {
var put adminapi.SettingsRequest
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/summary":
_ = json.NewEncoder(w).Encode(adminapi.Summary{})
case r.Method == "GET" && r.URL.Path == "/api/v1/settings":
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 3, Body: "router:\n retries: 2\n"})
case r.Method == "PUT" && r.URL.Path == "/api/v1/settings":
_ = json.NewDecoder(r.Body).Decode(&put)
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 4, Body: put.Body})
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
dir := t.TempDir()
cfg := filepath.Join(dir, "server.yaml")
_ = os.WriteFile(filepath.Join(dir, "token"), []byte("t"), 0o600)
_ = os.WriteFile(cfg, []byte("listen: \":8080\"\nagent_insecure: true\nadmin_listen: "+
strings.TrimPrefix(srv.URL, "http://")+"\nadmin_remote: true\nadmin_token_file: "+filepath.Join(dir, "token")+"\n"), 0o600)
var stop *cobra.Command
for _, c := range newSystemCmds() {
if c.Name() == "stop" {
stop = c
}
}
var out bytes.Buffer
stop.SetOut(&out)
stop.SetArgs([]string{"--config", cfg, "--note", "maintenance"})
if err := stop.ExecuteContext(context.Background()); err != nil {
t.Fatalf("%v: %s", err, out.String())
}
if put.Base != 3 || !strings.Contains(put.Body, "stopped: true") || !strings.Contains(put.Body, "retries: 2") ||
put.Note != "maintenance" {
t.Errorf("sent %+v", put)
}
if !strings.Contains(out.String(), "stopping") {
t.Errorf("%s", out.String())
}
}
// Started while the control plane has not yet taken the setting up: every
// model of a stopped system has the nothing it asks for, which is not
// serving.
func TestStartWaitsForTheControlPlaneToServe(t *testing.T) {
old := servicePoll
servicePoll = time.Millisecond
t.Cleanup(func() { servicePoll = old })
answers := []adminapi.Summary{
{Stopped: true, Models: adminapi.ModelTotals{Total: 2, Satisfied: 2}},
{Models: adminapi.ModelTotals{Total: 2, Satisfied: 0}},
{Models: adminapi.ModelTotals{Total: 2, Satisfied: 2}},
}
asked := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(answers[min(asked, len(answers)-1)])
asked++
}))
defer srv.Close()
var out bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&out)
cmd.SetContext(context.Background())
c := &ctlClient{base: srv.URL, http: srv.Client()}
if err := waitForService(cmd, func() *ctlClient { return c }, false); err != nil {
t.Fatal(err)
}
if asked != 3 {
t.Errorf("returned after %d answers: %s", asked, out.String())
}
}