Профиль — это отдельный сервер: свой мир, .ini, песочница, набор модов и лимит памяти JVM. Project Zomboid различает их по -servername, поэтому файлы профилей не пересекаются. Работает всегда один профиль: серверные файлы, игровые порты и файл запуска JVM общие, так что переключение разрешено только на остановленном сервере. - config: список профилей вместо одиночных server_name/admin_password/ java_memory; старые поля переносятся в первый профиль при загрузке; - supervisor: запускает активный профиль и выставляет его -Xmx на каждом старте, потому что файл запуска общий; - бэкапы: архив снимается с одного профиля (мир + его конфиги), имя профиля попадает в имя файла, ротация считается по профилям; - API профилей: список, создание с копированием настроек из другого профиля, правка, удаление и переключение активного; - панель: селектор профиля в шапке и вкладка со списком профилей. Удаление профиля убирает его только из панели: мир и конфиги остаются на диске, чтобы случайное нажатие не стоило сохранений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
5.2 KiB
Go
162 lines
5.2 KiB
Go
package config
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// TestLoadMigratesSingleServerConfig проверяет обновление с версии, где сервер
|
||
// был один: его настройки должны стать первым профилем, а старые поля —
|
||
// исчезнуть из файла.
|
||
func TestLoadMigratesSingleServerConfig(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||
legacy := strings.Join([]string{
|
||
"listen: 127.0.0.1:8080",
|
||
"server_name: myworld",
|
||
"admin_password: hunter2",
|
||
"java_memory: 8g",
|
||
"extra_args:",
|
||
" - -debug",
|
||
}, "\n")
|
||
if err := os.WriteFile(path, []byte(legacy), 0o600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
cfg, err := Load(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
if len(cfg.Profiles) != 1 {
|
||
t.Fatalf("получено %d профилей, ожидался 1: %+v", len(cfg.Profiles), cfg.Profiles)
|
||
}
|
||
p := cfg.Profiles[0]
|
||
if p.ID != "myworld" || p.AdminPassword != "hunter2" || p.JavaMemory != "8g" {
|
||
t.Errorf("настройки не перенесены в профиль: %+v", p)
|
||
}
|
||
if len(p.ExtraArgs) != 1 || p.ExtraArgs[0] != "-debug" {
|
||
t.Errorf("extra_args не перенесены: %v", p.ExtraArgs)
|
||
}
|
||
if cfg.Active != "myworld" {
|
||
t.Errorf("активный профиль = %q", cfg.Active)
|
||
}
|
||
|
||
// Файл должен быть переписан без устаревших полей.
|
||
raw, err := os.ReadFile(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
saved := string(raw)
|
||
for _, key := range []string{"server_name:", "admin_password:", "java_memory:", "extra_args:"} {
|
||
if strings.Contains(saved, "\n"+key) {
|
||
t.Errorf("в файле осталось устаревшее поле %s:\n%s", key, saved)
|
||
}
|
||
}
|
||
if !strings.Contains(saved, "active_profile: myworld") {
|
||
t.Errorf("активный профиль не записан:\n%s", saved)
|
||
}
|
||
}
|
||
|
||
func TestLoadCreatesDefaultProfile(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||
cfg, err := Load(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(cfg.Profiles) != 1 || cfg.Profiles[0].ID != "servertest" {
|
||
t.Fatalf("профиль по умолчанию не создан: %+v", cfg.Profiles)
|
||
}
|
||
if cfg.ActiveProfile().ID != "servertest" {
|
||
t.Errorf("активный профиль = %q", cfg.ActiveProfile().ID)
|
||
}
|
||
}
|
||
|
||
// TestLoadRepairsDanglingActive: активный профиль могли удалить руками из
|
||
// файла, панель не должна из-за этого падать.
|
||
func TestLoadRepairsDanglingActive(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||
raw := strings.Join([]string{
|
||
"active_profile: удалённый",
|
||
"profiles:",
|
||
" - id: vanilla",
|
||
" title: Ванилла",
|
||
}, "\n")
|
||
if err := os.WriteFile(path, []byte(raw), 0o600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
cfg, err := Load(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if cfg.Active != "vanilla" {
|
||
t.Errorf("активный профиль = %q, ожидался vanilla", cfg.Active)
|
||
}
|
||
if cfg.Profiles[0].JavaMemory == "" {
|
||
t.Error("профилю без java_memory не подставлено умолчание")
|
||
}
|
||
}
|
||
|
||
func TestProfileCRUD(t *testing.T) {
|
||
cfg := Default()
|
||
|
||
if err := cfg.AddProfile(Profile{ID: "modded", Title: "С модами"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cfg.AddProfile(Profile{ID: "modded"}); err == nil {
|
||
t.Error("дубликат профиля принят")
|
||
}
|
||
if err := cfg.AddProfile(Profile{ID: "плохой id"}); err == nil {
|
||
t.Error("недопустимый идентификатор принят")
|
||
}
|
||
|
||
if err := cfg.DeleteProfile("servertest"); err == nil {
|
||
t.Error("активный профиль удалён без переключения")
|
||
}
|
||
if err := cfg.SetActive("modded"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cfg.DeleteProfile("servertest"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cfg.DeleteProfile("modded"); err == nil {
|
||
t.Error("удалён единственный оставшийся профиль")
|
||
}
|
||
if err := cfg.SetActive("несуществующий"); err == nil {
|
||
t.Error("активирован несуществующий профиль")
|
||
}
|
||
}
|
||
|
||
func TestUpdateProfileKeepsID(t *testing.T) {
|
||
cfg := Default()
|
||
err := cfg.UpdateProfile("servertest", Profile{ID: "другой", Title: "Переименован", JavaMemory: "16g"})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
p := cfg.ActiveProfile()
|
||
if p.ID != "servertest" {
|
||
t.Errorf("идентификатор профиля изменился: %q", p.ID)
|
||
}
|
||
if p.Title != "Переименован" || p.JavaMemory != "16g" {
|
||
t.Errorf("настройки не сохранены: %+v", p)
|
||
}
|
||
}
|
||
|
||
func TestProfilePaths(t *testing.T) {
|
||
cfg := Default()
|
||
cfg.ZomboidDir = filepath.Join("/home", "pz", "Zomboid")
|
||
|
||
if got := cfg.ServerConfigPathFor("modded"); filepath.Base(got) != "modded.ini" {
|
||
t.Errorf("конфиг профиля: %s", got)
|
||
}
|
||
if got := cfg.SandboxConfigPathFor("modded"); filepath.Base(got) != "modded_SandboxVars.lua" {
|
||
t.Errorf("песочница профиля: %s", got)
|
||
}
|
||
if got := cfg.WorldDirFor("modded"); filepath.Base(got) != "modded" ||
|
||
!strings.Contains(filepath.ToSlash(got), "Saves/Multiplayer") {
|
||
t.Errorf("мир профиля: %s", got)
|
||
}
|
||
}
|