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
118 lines
3 KiB
Go
118 lines
3 KiB
Go
//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)
|
|
}
|
|
}
|