package main import ( "encoding/json" "errors" "os" "path/filepath" "strings" "testing" ) func TestBootTestPayloadIsWhatANewServerGets(t *testing.T) { dir := t.TempDir() game := filepath.Join(dir, "games", "demo") if err := os.MkdirAll(game, 0o755); err != nil { t.Fatal(err) } os.WriteFile(filepath.Join(game, "game.yaml"), []byte(`id: demo name: Demo defaultDockerImage: debian:12 installMethod: NONE defaultConfig: RCON_PORT: 25575 ENABLE_RCON: true RCON_PASSWORD: "" ServerPassword: "" AdminPassword: kept-by-owner MOTD: "{SERVER_NAME} on {PUBLIC_PORT_25565}" configSchema: properties: RCON_PORT: {type: integer} ENABLE_RCON: {type: boolean} RCON_PASSWORD: {type: string, x-generate: {type: password}} ServerPassword: {type: string} AdminPassword: {type: string} MOTD: {type: string} `), 0o644) out := filepath.Join(dir, "payload.json") if err := runBootTestPayload([]string{"-games", filepath.Join(dir, "games"), "-out", out, "demo"}); err != nil { t.Fatal(err) } raw, _ := os.ReadFile(out) var payload struct{ GameConfig map[string]any } if err := json.Unmarshal(raw, &payload); err != nil { t.Fatal(err) } c := payload.GameConfig if c["RCON_PORT"] != float64(25575) { t.Errorf("RCON_PORT changed to %v", c["RCON_PORT"]) } if c["ENABLE_RCON"] != true { t.Errorf("ENABLE_RCON changed to %v", c["ENABLE_RCON"]) } // Exactly what a new server gets: the x-generate password is made, a blank join password stays blank if s, _ := c["RCON_PASSWORD"].(string); !strings.HasPrefix(s, "boot-") { t.Errorf("x-generate RCON_PASSWORD not filled: %v", c["RCON_PASSWORD"]) } if c["ServerPassword"] != "" { t.Errorf("ServerPassword must stay empty, as for a new server: %v", c["ServerPassword"]) } if c["AdminPassword"] != "kept-by-owner" { t.Errorf("AdminPassword overwritten: %v", c["AdminPassword"]) } if c["MOTD"] != "gbr-boot-test on 25565" { t.Errorf("placeholders not resolved: %v", c["MOTD"]) } // Only for the builder's diagnosis: blank passwords filled too if err := runBootTestPayload([]string{"-fill-blank-passwords", "-games", filepath.Join(dir, "games"), "-out", out, "demo"}); err != nil { t.Fatal(err) } raw, _ = os.ReadFile(out) payload.GameConfig = nil if err := json.Unmarshal(raw, &payload); err != nil { t.Fatal(err) } if s, _ := payload.GameConfig["ServerPassword"].(string); !strings.HasPrefix(s, "boot-") { t.Errorf("-fill-blank-passwords: ServerPassword not filled: %v", payload.GameConfig["ServerPassword"]) } } func TestBootTestPayloadAsksForPublishedPortsAndHold(t *testing.T) { dir := t.TempDir() game := filepath.Join(dir, "example") os.MkdirAll(game, 0o755) os.WriteFile(filepath.Join(game, "game.yaml"), []byte("id: example\nname: Example\ndefaultDockerImage: x\ndefaultPorts:\n - 30000/udp\n - 30001:30001/tcp\n"), 0o644) os.WriteFile(filepath.Join(game, "test.yaml"), []byte("holdSec: 90\n"), 0o644) p, err := bootTestPayload(dir, "example", false) if err != nil { t.Fatal(err) } ports, _ := p["expectPorts"].([]any) if len(ports) != 2 || ports[0] != "30000/udp" || ports[1] != "30001:30001/tcp" { t.Errorf("expectPorts %v", p["expectPorts"]) } if p["holdSec"] != 90 || p["settleSec"] != 120 { t.Errorf("hold %v settle %v", p["holdSec"], p["settleSec"]) } os.WriteFile(filepath.Join(game, "test.yaml"), []byte("secrets:\n OwnerId: EXAMPLE_OWNER\n"), 0o644) _, err = bootTestPayload(dir, "example", false) var missing *missingSecretError if !errors.As(err, &missing) || missing.secret != "EXAMPLE_OWNER" { t.Errorf("missing secret: %v", err) } } func TestBootTestPayloadLeavesOutUnboundPorts(t *testing.T) { dir := t.TempDir() game := filepath.Join(dir, "example") os.MkdirAll(game, 0o755) os.WriteFile(filepath.Join(game, "game.yaml"), []byte("id: example\nname: Example\ndefaultDockerImage: x\ndefaultPorts:\n - 30000/tcp\n - 30001/udp\n"), 0o644) os.WriteFile(filepath.Join(game, "test.yaml"), []byte("unboundPorts:\n - 30001/udp\n"), 0o644) p, err := bootTestPayload(dir, "example", false) if err != nil { t.Fatal(err) } if ports := p["expectPorts"].([]any); len(ports) != 1 || ports[0] != "30000/tcp" { t.Errorf("expectPorts %v", ports) } }