В Build 42 mod.info лежит не в корне мода, а в папке версии: common, 42, 42.16. Сканер смотрел только в корень, и такие моды выглядели нескачанными с неизвестным Mod ID — включить их через панель было нельзя. На реальной мастерской находилось 237 модов из 399. Названия из мастерской фронтенд запрашивал одним списком, а эндпоинт не принимает больше двухсот идентификаторов за раз. У сборки на 284 пакета запрос падал целиком, ошибка молча проглатывалась, и вместо названий оставались одни номера. Теперь запрос идёт партиями, а полная неудача больше не остаётся незамеченной. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
5.5 KiB
Go
138 lines
5.5 KiB
Go
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)
|
||
|
||
// Обычная раскладка: <workshop id>/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 найден там, где его нет")
|
||
}
|
||
}
|