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
73
internal/motd/apply_test.go
Normal file
73
internal/motd/apply_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package motd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// setupDir legt ein update-motd.d-Verzeichnis unter einem Temp-Root an.
|
||||
func setupDir(t *testing.T, files map[string]string) string {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
dir := MotdDir(root)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, content := range files {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func TestValidateRejectsUnknownNames(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{"00-header": "#!/bin/sh\necho hi\n"})
|
||||
|
||||
err := Validate(root, Plan{Chmod: []ChmodOp{{Name: "../evil", Enable: true}}})
|
||||
if err == nil {
|
||||
t.Error("Pfad-Traversal sollte abgelehnt werden")
|
||||
}
|
||||
err = Validate(root, Plan{Chmod: []ChmodOp{{Name: "gibt-es-nicht", Enable: true}}})
|
||||
if err == nil {
|
||||
t.Error("chmod auf fehlende Datei sollte abgelehnt werden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsForeignOverwrite(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{"00-header": "#!/bin/sh\necho eigenes\n"})
|
||||
|
||||
err := Validate(root, Plan{Installs: []InstallOp{{Name: "00-header", Source: "footer", Enabled: true}}})
|
||||
if err == nil {
|
||||
t.Error("Install über fremdes Script sollte abgelehnt werden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsForeignRemove(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{"10-sysinfo": "#!/bin/sh\necho eigenes\n"})
|
||||
|
||||
err := Validate(root, Plan{Removes: []RemoveOp{{Name: "10-sysinfo"}}})
|
||||
if err == nil {
|
||||
t.Error("Remove eines fremden Scripts sollte abgelehnt werden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRenameCollision(t *testing.T) {
|
||||
root := setupDir(t, map[string]string{
|
||||
"00-a": "#!/bin/sh\n",
|
||||
"10-b": "#!/bin/sh\n",
|
||||
})
|
||||
|
||||
// b wird nicht wegbewegt → Ziel 10-b ist belegt.
|
||||
err := Validate(root, Plan{Renames: []RenameOp{{From: "00-a", To: "10-b"}}})
|
||||
if err == nil {
|
||||
t.Error("Kollision sollte abgelehnt werden")
|
||||
}
|
||||
|
||||
// Beide tauschen → zweiphasige Ausführung macht das frei.
|
||||
err = Validate(root, Plan{Renames: []RenameOp{{From: "00-a", To: "10-b"}, {From: "10-b", To: "00-a"}}})
|
||||
if err != nil {
|
||||
t.Errorf("Tausch sollte erlaubt sein: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue