The builder's boot test filled every blank password-like setting with a throwaway value. A real deploy only fills x-generate fields, so a draft whose server refuses to run with an empty join password passed the builder and CI, and then shut down on its first real deploy. The builder now boots drafts with boottest-payload -as-deployed: only x-generate fields are filled, everything else exactly as the template's defaults. A server that won't run with its own defaults fails the build, and the model is told that empty settings stay empty, so the value must come from a default or x-generate. CI's boot test is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBootTestPayloadOnlyFillsBlankPasswords(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"])
|
|
}
|
|
for _, k := range []string{"RCON_PASSWORD", "ServerPassword"} {
|
|
if s, _ := c[k].(string); !strings.HasPrefix(s, "boot-") {
|
|
t.Errorf("%s not filled: %v", k, c[k])
|
|
}
|
|
}
|
|
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"])
|
|
}
|
|
|
|
// As a new server gets it (what the AI builder boots): only x-generate is filled, a blank join password stays blank
|
|
if err := runBootTestPayload([]string{"-as-deployed", "-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["RCON_PASSWORD"].(string); !strings.HasPrefix(s, "boot-") {
|
|
t.Errorf("as deployed: x-generate RCON_PASSWORD not filled: %v", payload.GameConfig["RCON_PASSWORD"])
|
|
}
|
|
if payload.GameConfig["ServerPassword"] != "" {
|
|
t.Errorf("as deployed: ServerPassword should stay empty, got %v", payload.GameConfig["ServerPassword"])
|
|
}
|
|
}
|