Files
gbr-games/cmd/gbrgames/boottest_test.go
T
Greg ReedandClaude Opus 5.5 bb4fd3e11c
Validate game templates / validate (push) Successful in 20s
Validate game templates / boot-test (push) Successful in 3m19s
Validate game templates / publish (push) Skipped
fix(boot-test): fill {PUBLIC_*} in the startup command like the panel does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-08 01:39:57 +00:00

150 lines
5.1 KiB
Go

package main
import (
"time"
"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)
}
}
func TestSteamCacheStampAge(t *testing.T) {
now := time.Unix(1_800_000_000, 0)
if !stampWithin("1799990000\n", now, 24*time.Hour) {
t.Error("updated 10000s ago should be fresh")
}
if stampWithin("1700000000", now, 24*time.Hour) || stampWithin("", now, 24*time.Hour) || stampWithin("x", now, 24*time.Hour) {
t.Error("old, missing or garbled stamps are not fresh")
}
}
func TestBootTestPayloadFillsPublicPlaceholdersInStartupCommand(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\n"+
"defaultStartupCommand: ./server -Port={PUBLIC_PORT_7777} -ip={PUBLIC_IP}\ndefaultPorts:\n - 7777/udp\n"), 0o644)
p, err := bootTestPayload(dir, "example", false)
if err != nil {
t.Fatal(err)
}
if p["startupCommand"] != "./server -Port=7777 -ip=127.0.0.1" {
t.Errorf("startupCommand %v", p["startupCommand"])
}
}