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
118
internal/motd/apply_unix_test.go
Normal file
118
internal/motd/apply_unix_test.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//go:build unix
|
||||
|
||||
package motd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"motd-assist/internal/assets"
|
||||
)
|
||||
|
||||
func TestExecuteAppliesPlanAndRestore(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{
|
||||
"00-header": "#!/bin/sh\necho header\n",
|
||||
"10-sysinfo": "#!/bin/sh\necho sysinfo\n",
|
||||
})
|
||||
dir := MotdDir(root)
|
||||
if err := os.Chmod(filepath.Join(dir, "10-sysinfo"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
current, err := Discover(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Ziel: sysinfo vor header, sysinfo aktiv, header aus.
|
||||
desired := []DesiredEntry{
|
||||
{Name: "10-sysinfo", Enabled: true},
|
||||
{Name: "00-header", Enabled: false},
|
||||
}
|
||||
plan := Diff(current, desired)
|
||||
if plan.Empty() {
|
||||
t.Fatal("Plan sollte Änderungen enthalten")
|
||||
}
|
||||
if err := Execute(root, plan); err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
after, err := Discover(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(after) != 2 {
|
||||
t.Fatalf("nach Execute: %v", after)
|
||||
}
|
||||
if after[0].Name != "00-sysinfo" || !after[0].Enabled {
|
||||
t.Errorf("after[0] = %+v, want 00-sysinfo aktiv", after[0])
|
||||
}
|
||||
if after[1].Name != "10-header" || after[1].Enabled {
|
||||
t.Errorf("after[1] = %+v, want 10-header inaktiv", after[1])
|
||||
}
|
||||
if _, err := os.Stat(SnapshotPath(root)); err != nil {
|
||||
t.Errorf("Snapshot fehlt: %v", err)
|
||||
}
|
||||
|
||||
if err := Restore(root); err != nil {
|
||||
t.Fatalf("Restore: %v", err)
|
||||
}
|
||||
restored, err := Discover(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if restored[0].Name != "00-header" || !restored[0].Enabled {
|
||||
t.Errorf("restored[0] = %+v, want 00-header aktiv", restored[0])
|
||||
}
|
||||
if restored[1].Name != "10-sysinfo" || restored[1].Enabled {
|
||||
t.Errorf("restored[1] = %+v, want 10-sysinfo inaktiv", restored[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteInstallAndRemove(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{
|
||||
"00-header": "#!/bin/sh\necho header\n",
|
||||
"95-footer": "#!/bin/sh\ntrue\n", // wird unten als gebündelt ersetzt
|
||||
})
|
||||
dir := MotdDir(root)
|
||||
// echtes gebündeltes Script einsetzen, damit Remove validiert
|
||||
content, ok := assets.ScriptContent("footer")
|
||||
if !ok {
|
||||
t.Fatal("footer-Vorlage nicht gefunden")
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "95-footer"), []byte(content), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
current, err := Discover(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
desired := []DesiredEntry{
|
||||
{Name: "10-brand-header", Enabled: true, Install: "brand-header"},
|
||||
{Name: "00-header", Enabled: true},
|
||||
{Name: "95-footer", Remove: true},
|
||||
}
|
||||
if err := Execute(root, Diff(current, desired)); err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
after, err := Discover(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var names []string
|
||||
for _, s := range after {
|
||||
names = append(names, s.Name)
|
||||
}
|
||||
want := []string{"00-brand-header", "10-header"}
|
||||
if len(names) != 2 || names[0] != want[0] || names[1] != want[1] {
|
||||
t.Errorf("names = %v, want %v", names, want)
|
||||
}
|
||||
if !after[0].Bundled {
|
||||
t.Error("brand-header sollte als gebündelt erkannt werden")
|
||||
}
|
||||
if !after[0].Enabled || !after[1].Enabled {
|
||||
t.Errorf("Modi falsch: %+v", after)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue