package casefix import ( "context" "os" "path/filepath" "testing" ) // content собирает дерево мастерской с файлами в разном регистре. func content(t *testing.T) (root, dir string) { t.Helper() root = t.TempDir() dir = filepath.Join(root, "steamapps", "workshop", "content", pzAppID, "3722134990", "mods", "GunsOfMarz", "42.16", "media", "AnimSets") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } for _, name := range []string{"LoadShotgun_HB.xml", "aim_down.xml"} { if err := os.WriteFile(filepath.Join(dir, name), []byte(""), 0o644); err != nil { t.Fatal(err) } } return root, dir } // Проверять создание ссылок есть смысл только там, где регистр вообще // различается: на Windows и macOS чинить нечего. func skipCaseInsensitive(t *testing.T, result Result) { t.Helper() if result.CaseInsensitive { t.Skip("файловая система не различает регистр") } } func TestApplyLinksUppercaseNames(t *testing.T) { root, dir := content(t) result, err := Apply(context.Background(), []string{root}, nil) if err != nil { t.Fatal(err) } skipCaseInsensitive(t, result) if result.Created == 0 { t.Fatalf("не создано ни одной ссылки: %+v", result) } // Ссылка нужна и на файл, и на каждую папку пути: игра опускает в нижний // регистр путь целиком, включая имя мода. link := filepath.Join(dir, "loadshotgun_hb.xml") if data, err := os.ReadFile(link); err != nil || string(data) != "" { t.Fatalf("ссылка на файл не читается: %v", err) } modDir := filepath.Join(root, "steamapps", "workshop", "content", pzAppID, "3722134990", "mods", "gunsofmarz") if _, err := os.Stat(modDir); err != nil { t.Fatalf("нет ссылки на папку мода: %v", err) } if _, err := os.Stat(filepath.Join(modDir, "42.16", "media", "animsets", "loadshotgun_hb.xml")); err != nil { t.Fatalf("путь целиком строчными не собрался: %v", err) } } // Повторный запуск после докачки модов не должен ничего ломать. func TestApplyIsIdempotent(t *testing.T) { root, _ := content(t) first, err := Apply(context.Background(), []string{root}, nil) if err != nil { t.Fatal(err) } skipCaseInsensitive(t, first) second, err := Apply(context.Background(), []string{root}, nil) if err != nil { t.Fatal(err) } if second.Created != 0 { t.Errorf("второй проход создал ссылки заново: %d", second.Created) } if second.Existing != first.Created { t.Errorf("готовых ссылок %d, а создано было %d", second.Existing, first.Created) } } // Имя, которое уже занято настоящим файлом, не трогаем. func TestApplyKeepsExistingLowercaseFile(t *testing.T) { root, dir := content(t) path := filepath.Join(dir, "loadshotgun_hb.xml") if err := os.WriteFile(path, []byte("свой файл"), 0o644); err != nil { t.Fatal(err) } if _, err := Apply(context.Background(), []string{root}, nil); err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil || string(data) != "свой файл" { t.Fatalf("существующий файл перезаписан: %q, %v", data, err) } } func TestApplyWithoutWorkshopDir(t *testing.T) { if _, err := Apply(context.Background(), []string{t.TempDir()}, nil); err == nil { t.Fatal("ожидалась ошибка про ненайденную папку модов") } } // На Windows и macOS кнопка должна честно сказать, что чинить нечего, а не // пересчитывать все имена как «уже готовые». func TestApplyReportsCaseInsensitiveFilesystem(t *testing.T) { root, dir := content(t) if !caseInsensitive(dir) { t.Skip("файловая система различает регистр") } result, err := Apply(context.Background(), []string{root}, nil) if err != nil { t.Fatal(err) } if !result.CaseInsensitive { t.Error("нечувствительность к регистру не распознана") } if result.Created != 0 || result.Existing != 0 || result.Scanned != 0 { t.Errorf("дерево обошли впустую: %+v", result) } }