package module import ( "os" "path/filepath" "strings" "testing" ) // TestRenderWritesTemplate verifies the Go text/template pipeline: // - template file read from manifest.Dir/ // - values flow via .Values.key // - output written under dataPath/, 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) } } }