4ccccc6fe2
Self-hostable game server control panel: Go controller + agent, 26 game modules, embedded web UI. One-line install via install.sh / install.ps1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
package module
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestRenderWritesTemplate verifies the Go text/template pipeline:
|
|
// - template file read from manifest.Dir/<cf.Template>
|
|
// - values flow via .Values.key
|
|
// - output written under dataPath/<cf.Path>, with sub-dirs created
|
|
// - missing keys render as empty (missingkey=zero)
|
|
func TestRenderWritesTemplate(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Simulate a module directory with a template subdir.
|
|
if err := os.MkdirAll(filepath.Join(dir, "templates"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tmplPath := filepath.Join(dir, "templates", "demo.conf.tmpl")
|
|
tmplBody := "name={{ .Values.name }}\npassword={{ .Values.password }}\nmissing={{ .Values.nope }}END\n"
|
|
if err := os.WriteFile(tmplPath, []byte(tmplBody), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m := &Manifest{
|
|
ID: "demo",
|
|
Name: "Demo",
|
|
Dir: dir,
|
|
ConfigFiles: []ConfigFile{
|
|
{Path: "conf/demo.conf", Format: "kv", Template: "templates/demo.conf.tmpl"},
|
|
},
|
|
}
|
|
|
|
dataPath := filepath.Join(t.TempDir(), "instance-data")
|
|
values := map[string]string{
|
|
"name": "hello",
|
|
"password": "s3cret",
|
|
}
|
|
if err := Render(m, dataPath, values); err != nil {
|
|
t.Fatalf("Render: %v", err)
|
|
}
|
|
|
|
out, err := os.ReadFile(filepath.Join(dataPath, "conf", "demo.conf"))
|
|
if err != nil {
|
|
t.Fatalf("read rendered: %v", err)
|
|
}
|
|
got := string(out)
|
|
if !strings.Contains(got, "name=hello") {
|
|
t.Errorf("missing 'name=hello' in %q", got)
|
|
}
|
|
if !strings.Contains(got, "password=s3cret") {
|
|
t.Errorf("missing 'password=s3cret' in %q", got)
|
|
}
|
|
if !strings.Contains(got, "missing=END") {
|
|
t.Errorf("missing-key didn't render as empty in %q", got)
|
|
}
|
|
}
|
|
|
|
// TestRenderSkipsConfigFilesWithoutTemplate verifies a ConfigFile entry
|
|
// without a Template is left alone (operator edits it directly, for example
|
|
// via SFTP/file manager).
|
|
func TestRenderSkipsConfigFilesWithoutTemplate(t *testing.T) {
|
|
m := &Manifest{
|
|
ID: "demo",
|
|
Name: "Demo",
|
|
Dir: t.TempDir(),
|
|
ConfigFiles: []ConfigFile{
|
|
{Path: "manual.cfg", Format: "kv"}, // no Template
|
|
},
|
|
}
|
|
dataPath := filepath.Join(t.TempDir(), "instance-data")
|
|
if err := Render(m, dataPath, nil); err != nil {
|
|
t.Errorf("Render with no-template ConfigFile should not error: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dataPath, "manual.cfg")); err == nil {
|
|
t.Error("should not have written manual.cfg")
|
|
}
|
|
}
|
|
|
|
func TestGenerateSecretsUnique(t *testing.T) {
|
|
m := &Manifest{
|
|
Secrets: []Secret{
|
|
{Name: "telnet_password", Generated: true},
|
|
{Name: "admin_token", Generated: true},
|
|
{Name: "unused_static", Generated: false},
|
|
},
|
|
}
|
|
secrets, err := GenerateSecrets(m)
|
|
if err != nil {
|
|
t.Fatalf("GenerateSecrets: %v", err)
|
|
}
|
|
if len(secrets) != 2 {
|
|
t.Errorf("got %d secrets, want 2 (only Generated=true)", len(secrets))
|
|
}
|
|
if secrets["telnet_password"] == "" || secrets["admin_token"] == "" {
|
|
t.Errorf("empty secret values: %+v", secrets)
|
|
}
|
|
if secrets["telnet_password"] == secrets["admin_token"] {
|
|
t.Errorf("two secrets collided: %q", secrets["telnet_password"])
|
|
}
|
|
if _, ok := secrets["unused_static"]; ok {
|
|
t.Errorf("non-generated secret should not be in output")
|
|
}
|
|
}
|
|
|
|
// TestRenderAgainst7DTDTemplate is an integration smoke test that renders
|
|
// the shipped 7 Days to Die template with a plausible value set and asserts
|
|
// the resulting XML contains the expected property values.
|
|
func TestRenderAgainst7DTDTemplate(t *testing.T) {
|
|
m, err := LoadFile(filepath.Join("..", "..", "modules", "7dtd", "module.yaml"))
|
|
if err != nil {
|
|
t.Skipf("7dtd manifest not loadable: %v", err)
|
|
}
|
|
// The template path under the manifest may not exist yet during early
|
|
// development. Skip cleanly if so.
|
|
var tmplRef, outRel string
|
|
for _, cf := range m.ConfigFiles {
|
|
if cf.Template != "" {
|
|
tmplRef = filepath.Join(m.Dir, cf.Template)
|
|
outRel = cf.Path
|
|
break
|
|
}
|
|
}
|
|
if tmplRef == "" {
|
|
t.Skip("7dtd manifest has no config_files with templates")
|
|
}
|
|
if _, err := os.Stat(tmplRef); err != nil {
|
|
t.Skipf("7dtd template not present: %v", err)
|
|
}
|
|
|
|
dataPath := t.TempDir()
|
|
values := map[string]string{
|
|
"server_name": "Refuge Test",
|
|
"telnet_password": "test-pw-123",
|
|
"max_players": "16",
|
|
"world_seed": "Navezgane",
|
|
}
|
|
if err := Render(m, dataPath, values); err != nil {
|
|
t.Fatalf("Render 7dtd: %v", err)
|
|
}
|
|
out, err := os.ReadFile(filepath.Join(dataPath, outRel))
|
|
if err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
got := string(out)
|
|
for _, needle := range []string{"Refuge Test", "test-pw-123", `value="16"`} {
|
|
if !strings.Contains(got, needle) {
|
|
t.Errorf("rendered serverconfig.xml missing %q; got:\n%s", needle, got)
|
|
}
|
|
}
|
|
}
|