mirror init sets a machine up as the model mirror — the user, /srv/models, and a unit serving files on :8081 and the management API on :8082 — and server init --mirror points the control plane at it instead of running one. Before, that took a unit written by hand and an edit to server.yaml. The management API can fetch and delete models and answered anyone who could reach it. mirror serve --api-allow limits it to the addresses given and the machine itself; mirror init passes the control plane's. The unit text is shared with the one server init writes for a mirror beside it. With --no-node the starter model is still fetched and catalogued, unpinned, so it starts on the first node added, and the init form shows the mirror in use instead of offering to run one. Tried on three empty VMs: the mirror answered the control plane and refused the node, and the starter model came up on the node when it was added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
104 lines
3.7 KiB
Go
104 lines
3.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func typeIn(m tea.Model, s string) tea.Model {
|
|
return send(m, tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(s)})
|
|
}
|
|
|
|
func TestTheInitFormBecomesInitsSettings(t *testing.T) {
|
|
in := &serverInit{pkiDir: "/etc/llmgw/pki"}
|
|
var m tea.Model = newInitModel(in, []string{"10.0.0.140"}, true)
|
|
if v := m.View(); !strings.Contains(v, "Already in the certificate: 10.0.0.140") {
|
|
t.Fatalf("the addresses found are not shown:\n%s", v)
|
|
}
|
|
m = typeIn(m, "ops@example.com")
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab})
|
|
m = typeIn(m, "llm.example.com, 203.0.113.10")
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(" ")}) // PostgreSQL on
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyTab},
|
|
tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(" ")}) // starter model off
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyEnter})
|
|
|
|
if in.admin != "ops@example.com" || strings.Join(in.hosts, "|") != "llm.example.com|203.0.113.10" {
|
|
t.Errorf("admin %q hosts %q", in.admin, in.hosts)
|
|
}
|
|
if !in.postgres || in.noMirror || in.noNode || !in.noModel {
|
|
t.Errorf("toggles: postgres %v mirror %v node %v model %v", in.postgres, !in.noMirror, !in.noNode, !in.noModel)
|
|
}
|
|
if m.(initModel).form {
|
|
t.Error("the form stayed up after Set up")
|
|
}
|
|
}
|
|
|
|
func TestAnEmailThatIsNotOneIsSaidSo(t *testing.T) {
|
|
in := &serverInit{}
|
|
var m tea.Model = newInitModel(in, nil, true)
|
|
m = typeIn(m, "not an address")
|
|
for range fStart {
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab})
|
|
}
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyEnter})
|
|
if !m.(initModel).form || !strings.Contains(m.View(), "not an address") {
|
|
t.Errorf("a bad email was accepted:\n%s", m.View())
|
|
}
|
|
}
|
|
|
|
func TestInitsLinesAreFiledAndThePasswordKeptInView(t *testing.T) {
|
|
in := &serverInit{admin: "ops@example.com", pkiDir: "/etc/llmgw/pki"}
|
|
var m tea.Model = newInitModel(in, nil, false)
|
|
for _, l := range []string{
|
|
" ok system user llmgw",
|
|
" kept certificate authority in /etc/llmgw/pki",
|
|
" ok administrator ops@example.com (u1)",
|
|
"",
|
|
" password: Tr0ub4dor-3",
|
|
" shown once — sign in and change it from /profile",
|
|
"",
|
|
" .. downloading qwen2.5-0.5b-instruct into the mirror",
|
|
" .. 40%",
|
|
" .. 80%",
|
|
" !! an NVIDIA card is on the PCI bus but nvidia-smi does not answer",
|
|
"The control plane is up. Next:",
|
|
" llmgw ctl status",
|
|
} {
|
|
m = send(m, initLineMsg(l))
|
|
}
|
|
m = send(m, initDoneMsg{})
|
|
im := m.(initModel)
|
|
if im.password != "Tr0ub4dor-3" {
|
|
t.Errorf("password %q", im.password)
|
|
}
|
|
v := m.View()
|
|
for _, want := range []string{"✓ system user llmgw", "Tr0ub4dor-3", "Shown once", "80%", "! an NVIDIA card", "Back up /etc/llmgw/pki", "node add"} {
|
|
if !strings.Contains(v, want) {
|
|
t.Errorf("screen lacks %q:\n%s", want, v)
|
|
}
|
|
}
|
|
if strings.Contains(v, "40%") {
|
|
t.Error("progress lines piled up instead of replacing each other")
|
|
}
|
|
if strings.Contains(v, "llmgw ctl status") {
|
|
t.Error("the closing text was listed as a step")
|
|
}
|
|
}
|
|
|
|
func TestWithAMirrorElsewhereTheFormDoesNotOfferOne(t *testing.T) {
|
|
in := &serverInit{mirrorURL: "http://10.0.0.150:8082"}
|
|
var m tea.Model = newInitModel(in, nil, true)
|
|
if v := m.View(); !strings.Contains(v, "at http://10.0.0.150:8082, from --mirror") {
|
|
t.Errorf("the mirror in use is not shown:\n%s", v)
|
|
}
|
|
// Tab from the addresses goes past the mirror line to PostgreSQL, then
|
|
// the node.
|
|
m = send(m, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyTab}, tea.KeyMsg{Type: tea.KeyTab})
|
|
if f := m.(initModel).focus; f != fNode {
|
|
t.Errorf("focus %d, want the node toggle %d", f, fNode)
|
|
}
|
|
}
|