Files
llmgw/internal/cli/tui_setup_test.go
T
Ilker ManapandClaude Opus 5.5 38f2671ed9 Open setup screens or a console when llmgw runs bare in a terminal
With nothing installed, sudo llmgw asks for one machine or three, takes how
to reach the other two (ssh agent, key or password, and a sudo password used
only for the install), checks every machine first, shows the plan and its
command line, and installs. On a control plane it opens a console with the
cluster's state and the next steps. Machines are reached with Go's ssh so a
password works; the store's etcd and access rules use this machine's routed
address rather than a client-facing name.

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

120 lines
3.7 KiB
Go

package cli
import (
"strings"
"testing"
tea "github.com/charmbracelet/bubbletea"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/preflight"
)
func press(m tea.Model, keys ...string) tea.Model {
for _, k := range keys {
var msg tea.KeyMsg
switch k {
case "enter":
msg = tea.KeyMsg{Type: tea.KeyEnter}
case "tab":
msg = tea.KeyMsg{Type: tea.KeyTab}
case "down":
msg = tea.KeyMsg{Type: tea.KeyDown}
case "esc":
msg = tea.KeyMsg{Type: tea.KeyEsc}
case "right":
msg = tea.KeyMsg{Type: tea.KeyRight}
default:
msg = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(k)}
}
m, _ = m.Update(msg)
}
return m
}
func TestTheSetupAsksForWhatHighAvailabilityNeedsAndRefusesWhatWillNotDo(t *testing.T) {
m := tea.Model(newSetupModel("ctl", []string{"10.0.0.5"}))
m = press(m, "down", "enter")
if s := m.(setupModel); s.page != pAdmin {
t.Fatalf("high availability did not lead to the administrator: page %d", s.page)
}
// No email: refused, with a reason.
m = press(m, "tab", "tab", "enter")
if s := m.(setupModel); s.page != pAdmin || !strings.Contains(s.formErr, "email") {
t.Fatalf("went on without an administrator: %+v", s.formErr)
}
// The refusal puts the cursor back on the email.
m = press(m, "a@example.com", "tab", "tab", "enter")
s := m.(setupModel)
if s.page != pMachines {
t.Fatalf("page %d: %s", s.page, s.formErr)
}
// This machine's own address is not a database machine.
s.db[0].addr.SetValue("10.0.0.5")
s.db[0].user.SetValue("ubuntu")
s.db[0].auth = authAgent
s.db[1].addr.SetValue("10.0.0.7")
s.db[1].user.SetValue("ubuntu")
s.db[1].auth = authPassword
if err := s.validateMachines(); !strings.Contains(err, "this machine") {
t.Errorf("this machine as a database machine: %q", err)
}
s.db[0].addr.SetValue("10.0.0.6")
if err := s.validateMachines(); !strings.Contains(err, "password") {
t.Errorf("a password login with no password: %q", err)
}
s.db[1].secret.SetValue("pw")
if err := s.validateMachines(); err != "" {
t.Errorf("a valid pair refused: %s", err)
}
cmd := s.command()
for _, want := range []string{"--admin a@example.com", "--patroni ubuntu@10.0.0.6", "--patroni ubuntu@10.0.0.7", "--no-node"} {
if !strings.Contains(cmd, want) {
t.Errorf("command lacks %q:\n%s", want, cmd)
}
}
if strings.Contains(cmd, "pw") {
t.Error("the password is in the command line")
}
}
func TestChoosingOneMachineHandsOverToInit(t *testing.T) {
m := press(tea.Model(newSetupModel("ctl", nil)), "enter").(setupModel)
if !m.single {
t.Error("one machine did not hand over")
}
}
func TestTheChecksHoldTheSetupBackOnlyForAFailure(t *testing.T) {
m := newSetupModel("ctl", nil)
m.choice, m.page = 1, pCheck
m.report = preflight.Report{Findings: []preflight.Finding{{Machine: "a", Check: "disk", Level: preflight.Fail, Fix: "more"}}}
if s := press(m, "enter").(setupModel); s.page != pCheck {
t.Error("went on past a failed check")
}
m.report.Findings[0].Level = preflight.Warn
if s := press(m, "enter").(setupModel); s.page != pPlan {
t.Error("a warning held it back")
}
}
func TestTheConsoleOffersToReplicateOnlyAStoreThatIsNot(t *testing.T) {
m := consoleModel{}
if !strings.Contains(m.items()[1].label, "Replicate") {
t.Error("a single store is not offered replication")
}
m.store = true
for _, it := range m.items() {
if strings.Contains(it.label, "Replicate") {
t.Error("a replicated store is offered replication")
}
}
// A node is asked for, then the console closes with it to do.
var mm tea.Model = consoleModel{summary: "x", loaded: true}
mm = press(mm, "enter", "ubuntu@10.0.0.9", "enter")
if a := mm.(consoleModel).action; a.kind != "node" || a.targets[0] != "ubuntu@10.0.0.9" {
t.Errorf("%+v", a)
}
_ = adminapi.Summary{}
}