feat: add motd-assist TUI for managing dynamic MOTDs
Initial Go project built with Bubble Tea and Lip Gloss: - Live preview and management of /etc/update-motd.d scripts - Embedded presets (ubuntu-default, minimal, server-dashboard) - Bundled POSIX sh style scripts (brand header, sysinfo panel, footer) - Snapshot/restore safety around applying changes - CLI subcommands preview, apply, restore, list-presets - Unit tests for detect, sanitize, and apply logic
This commit is contained in:
commit
d56659e21a
34 changed files with 3260 additions and 0 deletions
109
internal/ui/model_test.go
Normal file
109
internal/ui/model_test.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"motd-assist/internal/motd"
|
||||
"motd-assist/internal/preset"
|
||||
)
|
||||
|
||||
func mustUpdate(m Model, msg tea.Msg) Model {
|
||||
next, _ := m.Update(msg)
|
||||
return next.(Model)
|
||||
}
|
||||
|
||||
func newTestModel(t *testing.T) Model {
|
||||
t.Helper()
|
||||
m := New(t.TempDir())
|
||||
m = mustUpdate(m, tea.WindowSizeMsg{Width: 120, Height: 40})
|
||||
return m
|
||||
}
|
||||
|
||||
func TestViewRendersAllPanes(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
scripts := []motd.Script{
|
||||
{Name: "00-header", Path: "/x/00-header", Enabled: true},
|
||||
{Name: "10-sysinfo", Path: "/x/10-sysinfo", Enabled: false},
|
||||
}
|
||||
m = mustUpdate(m, discoverMsg{scripts: scripts, host: motd.HostInfo{Hostname: "myhost", Distro: "Ubuntu 24.04", PAM: true}})
|
||||
m = mustUpdate(m, previewMsg{version: m.previewVersion, text: "Willkommen auf myhost"})
|
||||
|
||||
view := m.View()
|
||||
for _, want := range []string{"Presets", "Scripte", "Live-Vorschau", "00-header", "10-sysinfo", "Willkommen auf myhost", "myhost"} {
|
||||
if !strings.Contains(view, want) {
|
||||
t.Errorf("View enthält %q nicht", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToggleAndPlan(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
scripts := []motd.Script{
|
||||
{Name: "00-header", Path: "/x/00-header", Enabled: true},
|
||||
{Name: "10-sysinfo", Path: "/x/10-sysinfo", Enabled: false},
|
||||
}
|
||||
m = mustUpdate(m, discoverMsg{scripts: scripts})
|
||||
|
||||
// Fokus auf Scripte wechseln und erstes Script ausschalten.
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyTab})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeySpace})
|
||||
|
||||
plan := m.currentPlan()
|
||||
if len(plan.Chmod) != 1 || plan.Chmod[0].Name != "00-header" || plan.Chmod[0].Enable {
|
||||
t.Errorf("Chmod = %+v, want 00-header aus", plan.Chmod)
|
||||
}
|
||||
if markers := m.markersByRow(); markers["00-header"] != "aus" {
|
||||
t.Errorf("Marker = %q, want aus", markers["00-header"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveRowChangesOrder(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
scripts := []motd.Script{
|
||||
{Name: "00-header", Path: "/x", Enabled: true},
|
||||
{Name: "10-sysinfo", Path: "/x", Enabled: true},
|
||||
}
|
||||
m = mustUpdate(m, discoverMsg{scripts: scripts})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyTab})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("d")}) // nach unten
|
||||
|
||||
if m.working[0].Name != "10-sysinfo" || m.working[1].Name != "00-header" {
|
||||
t.Errorf("Reihenfolge = %v, %v", m.working[0].Name, m.working[1].Name)
|
||||
}
|
||||
if plan := m.currentPlan(); len(plan.Renames) != 2 {
|
||||
t.Errorf("Renames = %+v, want 2", plan.Renames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPresetMarksPending(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
scripts := []motd.Script{{Name: "00-header", Path: "/x", Enabled: false}}
|
||||
m = mustUpdate(m, discoverMsg{scripts: scripts})
|
||||
m = mustUpdate(m, presetsMsg{presets: []preset.Preset{{Name: "p1", Scripts: []preset.Entry{{Name: "00-header", Enabled: true}}}}})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyEnter}) // erstes Preset laden
|
||||
|
||||
if plan := m.currentPlan(); len(plan.Chmod) != 1 || !plan.Chmod[0].Enable {
|
||||
t.Errorf("Plan = %+v, want 00-header an", plan)
|
||||
}
|
||||
if m.status == "" {
|
||||
t.Error("Statusmeldung nach Preset-Laden erwartet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscardResetsWorking(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
scripts := []motd.Script{{Name: "00-header", Path: "/x", Enabled: true}}
|
||||
m = mustUpdate(m, discoverMsg{scripts: scripts})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyTab})
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeySpace}) // ausschalten
|
||||
if m.currentPlan().Empty() {
|
||||
t.Fatal("Plan sollte Änderungen haben")
|
||||
}
|
||||
m = mustUpdate(m, tea.KeyMsg{Type: tea.KeyEsc})
|
||||
if !m.currentPlan().Empty() {
|
||||
t.Errorf("nach esc sollte der Plan leer sein, ist %+v", m.currentPlan())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue