package main import ( "encoding/json" "strings" "testing" ) func TestDraftCompletenessNamesMissingSchemaEntries(t *testing.T) { // The Valheim draft that went round in circles: settings in defaultConfig, no configSchema game := map[string]any{ "defaultPorts": []any{"2456/udp"}, "requiredMemoryMb": json.Number("4096"), "defaultConfig": map[string]any{"ServerName": "x", "ServerPassword": "", "MaxPlayers": json.Number("8")}, } problems := strings.Join(draftCompleteness(game, map[string]any{"settleSec": json.Number("120")}), "\n") if !strings.Contains(problems, "no configSchema.properties entry") || !strings.Contains(problems, "MaxPlayers, ServerName, ServerPassword") { t.Errorf("want the missing schema entries named, got:\n%s", problems) } game["configSchema"] = map[string]any{"properties": map[string]any{ "ServerName": map[string]any{"title": "Name", "description": "d", "type": "string", "category": "System & General"}, "ServerPassword": map[string]any{"title": "Password", "description": "d", "type": "string", "category": "Security & Access"}, "MaxPlayers": map[string]any{"title": "Players", "description": "d", "type": "integer", "category": "System & General"}, }} if p := draftCompleteness(game, map[string]any{"settleSec": json.Number("120")}); len(p) != 0 { t.Errorf("complete draft still has problems: %v", p) } } func TestUnreadSettings(t *testing.T) { facts := &imageFacts{EnvRead: []string{"SERVER_NAME", "SERVER_PASS", "SERVER_PUBLIC"}, Env: map[string]string{"TZ": "UTC"}} game := map[string]any{"defaultConfig": map[string]any{"ServerName": "x", "ServerPassword": "", "Tz": "UTC"}} problems := unreadSettings(game, facts) if len(problems) != 1 || !strings.Contains(problems[0], "SERVER_PASSWORD") || !strings.Contains(problems[0], "did you mean SERVER_PASS") { t.Errorf("got %v", problems) } // Settings written to a config file aren't environment-only, so nothing to check game["configFilePath"] = "server.cfg" if p := unreadSettings(game, facts); p != nil { t.Errorf("config-file game: %v", p) } } func TestSavesOutsideVolumes(t *testing.T) { // Valheim's real first boot: its own files in ~/.config, plus Steam client logs (noise) valheim := &bootReport{ContainerChanges: []string{ "A /home/steam/.config/unity3d/IronGate/Valheim/adminlist.txt", "A /home/steam/.config/unity3d/IronGate/Valheim/bannedlist.txt", "A /home/steam/Steam/logs/connection_log_2456.txt", }} game := map[string]any{"defaultStartupCommand": "./valheim_server.x86_64 -nographics -batchmode -world X"} problems := savesOutsideVolumes(game, valheim) if len(problems) != 1 || !strings.Contains(problems[0], "/home/steam/.config/unity3d/IronGate/Valheim") || strings.Contains(problems[0], "Steam/logs") { t.Fatalf("got %v", problems) } // Fixed with a save-directory option under /server game["defaultStartupCommand"] = "./valheim_server.x86_64 -nographics -batchmode -world X -savedir /server/saves" if p := savesOutsideVolumes(game, valheim); p != nil { t.Errorf("redirected template still flagged: %v", p) } // ARK's real first boot: only Proton's cache, which is not game data ark := &bootReport{ContainerChanges: []string{"A /home/gameserver/.cache/umu-protonfixes/protonfixes_test.log"}} if p := savesOutsideVolumes(map[string]any{}, ark); p != nil { t.Errorf("ARK proton cache flagged: %v", p) } } func TestImageUnusable(t *testing.T) { code := 2 r := &bootReport{User: "1000:1000", ExitCode: &code, RanSeconds: 2.4, ErrorLines: []logLine{ {Text: "ln: failed to create symbolic link '/etc/localtime': Permission denied"}, }} if why := imageUnusable(r); !strings.Contains(why, "needs root") { t.Errorf("want a needs-root verdict, got %q", why) } // A server that ran for minutes and then stopped is not this case r.RanSeconds = 600 if why := imageUnusable(r); why != "" { t.Errorf("long run flagged: %q", why) } } func TestDraftCompletenessRejectsSecrets(t *testing.T) { // The Valheim draft that couldn't be boot-tested: the password mapped to a CI secret nobody created test := map[string]any{"settleSec": json.Number("120"), "secrets": map[string]any{"Password": "VALHEIM_TEST_PASSWORD"}} problems := strings.Join(draftCompleteness(map[string]any{}, test), "\n") if !strings.Contains(problems, "test.yaml has 'secrets' (Password)") { t.Errorf("got:\n%s", problems) } }