package pzconfig import ( "os" "path/filepath" "testing" ) // TestScanInstalledMods проверяет обе раскладки мастерской и локальную папку // модов, а также то, что дубликаты схлопываются. func TestScanInstalledMods(t *testing.T) { root := t.TempDir() content := filepath.Join(root, "server", "steamapps", "workshop", "content", pzAppID) // Обычная раскладка: /mods/<папка>/mod.info writeModInfo(t, filepath.Join(content, "111", "mods", "BetterSorting"), "name=Better Sorting\nid=BetterSorting\ndescription=Сортировка\n") // Второй мод в том же пакете мастерской. writeModInfo(t, filepath.Join(content, "111", "mods", "BetterSortingCore"), "name=Better Sorting Core\nid=BetterSortingCore\n") // Раскладка без промежуточной папки mods. writeModInfo(t, filepath.Join(content, "222", "ModOptions"), "name=Mod Options\nid=ModOptions\n") // Локальный мод, положенный руками. local := filepath.Join(root, "Zomboid", "mods") writeModInfo(t, filepath.Join(local, "MyTweak"), "name=My Tweak\nid=MyTweak\n") // Тот же мод, что и в мастерской: должен попасть в список один раз. writeModInfo(t, filepath.Join(local, "BetterSortingCopy"), "name=Better Sorting\nid=BetterSorting\n") // Папка без mod.info игнорируется. if err := os.MkdirAll(filepath.Join(content, "333", "mods", "empty"), 0o755); err != nil { t.Fatal(err) } mods := ScanInstalledMods([]string{filepath.Join(root, "server")}, local) byID := make(map[string]InstalledMod, len(mods)) for _, m := range mods { if _, dup := byID[m.ModID]; dup { t.Errorf("мод %s встретился дважды", m.ModID) } byID[m.ModID] = m } if len(byID) != 4 { t.Fatalf("найдено %d модов, ожидалось 4: %v", len(byID), byID) } if got := byID["BetterSorting"]; got.WorkshopID != "111" || got.Name != "Better Sorting" { t.Errorf("BetterSorting разобран неверно: %+v", got) } if got := byID["ModOptions"]; got.WorkshopID != "222" { t.Errorf("раскладка без папки mods не распознана: %+v", got) } if got := byID["MyTweak"]; got.WorkshopID != "" { t.Errorf("локальному моду проставлен Workshop ID: %+v", got) } } func TestReadModInfoRequiresID(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "mod.info") if err := os.WriteFile(path, []byte("name=Без идентификатора\n"), 0o644); err != nil { t.Fatal(err) } if _, ok := readModInfo(path); ok { t.Error("мод без id принят") } } func writeModInfo(t *testing.T, dir, content string) { t.Helper() if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "mod.info"), []byte(content), 0o644); err != nil { t.Fatal(err) } } // В Build 42 mod.info лежит не в корне мода, а в папке версии. Мод из такой // раскладки должен находиться, иначе панель считает его нескачанным. func TestScanInstalledModsBuild42Layout(t *testing.T) { root := t.TempDir() content := filepath.Join(root, "server", "steamapps", "workshop", "content", pzAppID) writeModInfo(t, filepath.Join(content, "111", "mods", "Wipes", "common"), "name=Cleaning Wipes\nid=dustinguished_cleaning_wipes\n") // Мод с несколькими версиями: берём самую свежую, а не common. mod := filepath.Join(content, "222", "mods", "NewMusicG90s") writeModInfo(t, filepath.Join(mod, "common"), "name=Mixtape (common)\nid=NewMusicG90s\n") writeModInfo(t, filepath.Join(mod, "42"), "name=Mixtape (42)\nid=NewMusicG90s\n") writeModInfo(t, filepath.Join(mod, "42.16"), "name=Mixtape (42.16)\nid=NewMusicG90s\n") mods := ScanInstalledMods([]string{filepath.Join(root, "server")}, "") byID := make(map[string]InstalledMod, len(mods)) for _, m := range mods { byID[m.ModID] = m } if len(byID) != 2 { t.Fatalf("найдено %d модов, ожидалось 2: %v", len(byID), byID) } if got := byID["dustinguished_cleaning_wipes"]; got.Name != "Cleaning Wipes" || got.WorkshopID != "111" { t.Errorf("мод из common разобран неверно: %+v", got) } if got := byID["NewMusicG90s"]; got.Name != "Mixtape (42.16)" { t.Errorf("взята не самая свежая версия: %+v", got) } // Dir указывает на папку мода, а не на папку версии: так его удобнее найти. if got := byID["NewMusicG90s"]; filepath.Base(got.Dir) != "NewMusicG90s" { t.Errorf("Dir указывает не на папку мода: %+v", got) } } // Корневой mod.info главнее версий: так раскладывают моды Build 41. func TestModInfoPathPrefersRoot(t *testing.T) { mod := t.TempDir() writeModInfo(t, mod, "name=Root\nid=Root\n") writeModInfo(t, filepath.Join(mod, "42"), "name=Version\nid=Version\n") path, ok := modInfoPath(mod) if !ok { t.Fatal("mod.info не найден") } if filepath.Dir(path) != mod { t.Errorf("взят mod.info из папки версии: %s", path) } } func TestModInfoPathWithoutModInfo(t *testing.T) { mod := t.TempDir() if err := os.MkdirAll(filepath.Join(mod, "media"), 0o755); err != nil { t.Fatal(err) } if _, ok := modInfoPath(mod); ok { t.Error("mod.info найден там, где его нет") } }