From 7237aee744fc1b53005d8b8f75372588c95ea0b6 Mon Sep 17 00:00:00 2001 From: Carsten Abele Date: Fri, 14 Aug 2026 13:22:35 +0200 Subject: [PATCH] fix(cli): Test las die Konfiguration des Hosts statt einer Attrappe TestRunNoArgsDefaultsToServe rief run(nil) auf und griff damit auf den Default-Pfad /etc/vpnportal/config.yaml zu. Auf einem Entwicklungsrechner existiert der nicht, der Test war dort gruen. Auf einem Zielhost mit installierter Konfiguration las er dagegen die Produktivkonfiguration und loeste echte Verbindungsversuche zu Firewall und Domain Controllern aus. Die Kommandoauswahl ist jetzt als reine Funktion commandFor testbar. Kein Test ruft serve oder check mehr ohne expliziten --config-Pfad auf. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68 --- cmd/vpnportal/main.go | 13 ++++++--- cmd/vpnportal/main_test.go | 60 +++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/cmd/vpnportal/main.go b/cmd/vpnportal/main.go index 842bed6..26862c9 100644 --- a/cmd/vpnportal/main.go +++ b/cmd/vpnportal/main.go @@ -21,12 +21,17 @@ func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } -func run(args []string, stdout, stderr io.Writer) int { - cmd := "serve" - rest := args +// commandFor waehlt das Unterkommando. Fehlt es oder beginnt das erste +// Argument mit '-', gilt serve als Vorgabe. +func commandFor(args []string) (cmd string, rest []string) { if len(args) > 0 && len(args[0]) > 0 && args[0][0] != '-' { - cmd, rest = args[0], args[1:] + return args[0], args[1:] } + return "serve", args +} + +func run(args []string, stdout, stderr io.Writer) int { + cmd, rest := commandFor(args) switch cmd { case "version": diff --git a/cmd/vpnportal/main_test.go b/cmd/vpnportal/main_test.go index fe02445..e1e8ad0 100644 --- a/cmd/vpnportal/main_test.go +++ b/cmd/vpnportal/main_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "path/filepath" "strings" "testing" ) @@ -30,14 +31,57 @@ func TestRunUnknownCommand(t *testing.T) { } } -func TestRunNoArgsDefaultsToServe(t *testing.T) { - var out, errOut bytes.Buffer - // serve ohne --config muss mit Fehler abbrechen, nicht mit "unknown command" - code := run(nil, &out, &errOut) - if code == 2 { - t.Fatalf("no args must default to serve, not unknown-command") +// commandFor wird bewusst ohne Seiteneffekte getestet. Ein Test, der run(nil) +// aufruft, wuerde den Default-Konfigpfad /etc/vpnportal/config.yaml lesen — +// auf einem Zielhost also die Produktivkonfiguration, samt Verbindungsversuch +// zu Firewall und Domain Controllern. +func TestCommandForDispatch(t *testing.T) { + cases := []struct { + name string + args []string + wantCmd string + wantRest []string + }{ + {"ohne Argumente", nil, "serve", nil}, + {"leere Liste", []string{}, "serve", []string{}}, + {"nur Flag", []string{"--config", "/x.yaml"}, "serve", []string{"--config", "/x.yaml"}}, + {"kurzes Flag", []string{"-h"}, "serve", []string{"-h"}}, + {"version", []string{"version"}, "version", []string{}}, + {"serve mit Flag", []string{"serve", "--config", "/x"}, "serve", []string{"--config", "/x"}}, + {"check mit Flag", []string{"check", "--test-auth", "u"}, "check", []string{"--test-auth", "u"}}, + {"unbekannt", []string{"frobnicate"}, "frobnicate", []string{}}, + {"leeres Argument", []string{""}, "serve", []string{""}}, } - if !strings.Contains(errOut.String(), "config") { - t.Errorf("stderr should complain about missing config, got %q", errOut.String()) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + cmd, rest := commandFor(tc.args) + if cmd != tc.wantCmd { + t.Errorf("cmd = %q, want %q", cmd, tc.wantCmd) + } + if len(rest) != len(tc.wantRest) { + t.Fatalf("rest = %q, want %q", rest, tc.wantRest) + } + for i := range rest { + if rest[i] != tc.wantRest[i] { + t.Errorf("rest[%d] = %q, want %q", i, rest[i], tc.wantRest[i]) + } + } + }) + } +} + +func TestRunServeReportsMissingConfigPath(t *testing.T) { + // Expliziter Pfad: der Test darf niemals die Konfiguration des Hosts lesen. + missing := filepath.Join(t.TempDir(), "gibtsnicht.yaml") + var out, errOut bytes.Buffer + code := run([]string{"--config", missing}, &out, &errOut) + if code == 2 { + t.Fatal("ein fuehrendes Flag muss auf serve abgebildet werden, nicht auf unknown-command") + } + if code == 0 { + t.Fatal("fehlende Konfiguration muss zum Abbruch fuehren") + } + if !strings.Contains(errOut.String(), missing) { + t.Errorf("stderr muss den Pfad nennen, got %q", errOut.String()) } }