The second Valheim build stopped before its boot test: told that test.yaml's password "looks like a credential; map it under 'secrets'", the model mapped it to a CI secret nobody created, and boottest-payload's missing-secret exit ended the build. - The validator now says to remove a test password first (boot tests fill password settings with a throwaway value); secrets only when the server can't start without a real one. - Drafts with test.yaml 'secrets' fail the draft checks. - build turns boottest-payload's missing-secret exit into feedback for the model instead of an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.3 KiB
Go
96 lines
4.3 KiB
Go
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)
|
|
}
|
|
}
|