Befehle login, servers, distros, profiles, presets, vm, lxc, template list|build, batch, status, logs --follow, cancel. Renderer im Aufbau der Vorgabe (zeichengenau per Golden-File), 24-bit/256/ohne Farbe, --ascii, --quiet, --json, --dry-run; live im Terminal, zeilenweise ohne. SSE mit Wiederverbinden und Polling als Rückfall. Typen aus openapi.json per oapi-codegen; Release-Builds per cli/bauen.sh. Refs #013 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.2 KiB
Go
119 lines
4.2 KiB
Go
package ausgabe
|
|
|
|
import (
|
|
"flag"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var aktualisieren = flag.Bool("update", false, "Golden-Dateien neu schreiben")
|
|
|
|
// Die Beispielausgabe aus der Vorgabe.
|
|
var mockPhasen = []Phase{
|
|
{Titel: "Prüfungen", Status: "ERLEDIGT", Detail: "VMID 120 frei · local-zfs · vmbr0 VLAN 20"},
|
|
{Titel: "Klonen", Status: "ERLEDIGT", Detail: "full clone 9013 → 120 · Disk auf 40G"},
|
|
{Titel: "cloud-init", Status: "ERLEDIGT", Detail: "user florian · SSH-Key · Profile base, docker"},
|
|
{Titel: "Start", Status: "ERLEDIGT", Detail: "VM 120 läuft"},
|
|
{Titel: "Guest-Agent", Status: "ERLEDIGT", Detail: "antwortet nach 18s"},
|
|
{Titel: "Provisionierung", Status: "LAEUFT", Detail: "cloud-init läuft", Laufzeit: 72 * time.Second},
|
|
}
|
|
|
|
var mockBox = Box{
|
|
Ok: true,
|
|
Titel: "web01 bereit",
|
|
Dauer: "1m 54s",
|
|
Zeilen: [][2]string{
|
|
{"VMID", "120"},
|
|
{"IP", "10.0.20.15"},
|
|
{"Profile", "base, docker"},
|
|
{"SSH", "ssh florian@10.0.20.15"},
|
|
},
|
|
}
|
|
|
|
func mockAusgabe(s Stil) string {
|
|
zeilen := []string{s.Kopf("1.0", "Server rz1 · Node pve01 · Debian 13 (Template 9013)", false), ""}
|
|
zeilen = append(zeilen, s.PhasenZeilen(mockPhasen, 0)...)
|
|
zeilen = append(zeilen, "")
|
|
zeilen = append(zeilen, s.BoxZeilen(mockBox)...)
|
|
return strings.Join(zeilen, "\n") + "\n"
|
|
}
|
|
|
|
func golden(t *testing.T, name, ist string) {
|
|
t.Helper()
|
|
pfad := filepath.Join("testdata", name)
|
|
if *aktualisieren {
|
|
if err := os.WriteFile(pfad, []byte(ist), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
soll, err := os.ReadFile(pfad)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ist != string(soll) {
|
|
t.Errorf("%s weicht ab.\n--- soll\n%s\n--- ist\n%s", name, soll, ist)
|
|
}
|
|
}
|
|
|
|
// Ohne Farbe muss die Ausgabe zeichengenau dem Mockup der Vorgabe entsprechen.
|
|
// mockup.txt ist von Hand aus der Vorgabe übernommen und wird nie mit -update überschrieben.
|
|
func TestMockupZeichengenau(t *testing.T) {
|
|
soll, err := os.ReadFile(filepath.Join("testdata", "mockup.txt"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ist := mockAusgabe(Stil{}); ist != string(soll) {
|
|
t.Errorf("weicht vom Mockup ab.\n--- soll\n%s\n--- ist\n%s", soll, ist)
|
|
}
|
|
}
|
|
|
|
func TestFarbenUndAscii(t *testing.T) {
|
|
golden(t, "mockup-24bit.golden", mockAusgabe(Stil{Farbe: Echtfarben}))
|
|
golden(t, "mockup-256.golden", mockAusgabe(Stil{Farbe: Farben256}))
|
|
golden(t, "mockup-ascii.golden", mockAusgabe(Stil{ASCII: true}))
|
|
}
|
|
|
|
func TestFehlerUndDryRun(t *testing.T) {
|
|
s := Stil{}
|
|
zeilen := []string{s.Kopf("1.0", "Server rz1 · Node pve01 · Debian 13", true), ""}
|
|
zeilen = append(zeilen, s.PhasenZeilen([]Phase{
|
|
{Titel: "Prüfungen", Status: "ERLEDIGT", Detail: "VMID 120 frei"},
|
|
{Titel: "Template", Status: "UEBERSPRUNGEN", Detail: "vorhanden (9013)"},
|
|
{Titel: "Klonen", Status: "FEHLER", Ursache: "Storage local-zfs ist voll.", Hinweis: "Anderen Storage wählen."},
|
|
}, 0)...)
|
|
zeilen = append(zeilen, "")
|
|
zeilen = append(zeilen, s.AufrufZeilen([]Aufruf{
|
|
{Methode: "POST", Pfad: "/nodes/pve01/qemu/9013/clone", Parameter: map[string]any{"newid": 120, "full": 1, "name": "web01"}},
|
|
{Methode: "PUT", Pfad: "/nodes/pve01/qemu/120/config", Parameter: map[string]any{"sshkeys": strings.Repeat("A", 80), "description": "Zeile 1\nZeile 2"}},
|
|
})...)
|
|
zeilen = append(zeilen, "")
|
|
zeilen = append(zeilen, s.BoxZeilen(Box{
|
|
Titel: "web01 fehlgeschlagen", Dauer: "12s",
|
|
Zeilen: [][2]string{{"Phase", "Klonen"}, {"Ursache", "Storage local-zfs ist voll."}, {"Hinweis", "Anderen Storage wählen und die VM mit einem sehr langen Hinweis neu ausrollen."}},
|
|
})...)
|
|
golden(t, "fehler-dryrun.golden", strings.Join(zeilen, "\n")+"\n")
|
|
}
|
|
|
|
func TestDauer(t *testing.T) {
|
|
for d, soll := range map[time.Duration]string{
|
|
0: "0s", 18 * time.Second: "18s", 72 * time.Second: "1m 12s", 65 * time.Second: "1m 05s",
|
|
3780 * time.Second: "1h 03m", 1499 * time.Millisecond: "1s",
|
|
} {
|
|
if ist := Dauer(d); ist != soll {
|
|
t.Errorf("Dauer(%v) = %q, soll %q", d, ist, soll)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestXterm256(t *testing.T) {
|
|
// Teal #2DD4BF → 43 (#00d7af), Paper #E6EDF3 → 254 (Graustufe #e4e4e4), Rot #F87171 → 203 (#ff5f5f).
|
|
for f, soll := range map[rgb]int{teal: 43, paper: 254, rot: 203} {
|
|
if ist := xterm256(f); ist != soll {
|
|
t.Errorf("xterm256(%v) = %d, soll %d", f, ist, soll)
|
|
}
|
|
}
|
|
}
|