go_games_collection/corpsestarch_save_test.go

172 lines
6.4 KiB
Go

package main
import (
"os"
"testing"
)
// withTempConfigDir подменяет XDG_CONFIG_HOME на временную — так
// os.UserConfigDir() на Linux/macOS будет смотреть в неё, и тесты
// не трогают реальный конфиг пользователя.
func withTempConfigDir(t *testing.T) {
t.Helper()
dir := t.TempDir()
oldXDG, hadXDG := os.LookupEnv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", dir)
t.Cleanup(func() {
if hadXDG {
os.Setenv("XDG_CONFIG_HOME", oldXDG)
} else {
os.Unsetenv("XDG_CONFIG_HOME")
}
})
}
func TestLoadCorpseStarchGameWithNoSaveFile(t *testing.T) {
withTempConfigDir(t)
_, err := LoadCorpseStarchGame()
if err != ErrCorpseStarchNoSave {
t.Fatalf("ожидалась ErrCorpseStarchNoSave при отсутствии файла, получено: %v", err)
}
}
func TestSaveAndLoadRoundTrip(t *testing.T) {
withTempConfigDir(t)
g := NewCorpseStarchGame(csT(0))
g.Starch = 123.5
g.TotalEarned = 999
g.ClickYield = 4
g.ClickUpgrades = 3
g.ServitorCounts[0] = 5
g.ServitorCounts[1] = 2
g.Corruption = 2
g.MissionsDone = 1
g.Log = []string{"a", "b", "c"}
if err := SaveCorpseStarchGame(g); err != nil {
t.Fatalf("неожиданная ошибка сохранения: %v", err)
}
loaded, err := LoadCorpseStarchGame()
if err != nil {
t.Fatalf("неожиданная ошибка загрузки: %v", err)
}
if loaded.Starch != g.Starch || loaded.TotalEarned != g.TotalEarned {
t.Errorf("запас/накопленное не совпадают после загрузки: %+v vs %+v", loaded, g)
}
if loaded.ClickYield != g.ClickYield || loaded.ClickUpgrades != g.ClickUpgrades {
t.Errorf("параметры клика не совпадают после загрузки")
}
if loaded.ServitorCounts[0] != 5 || loaded.ServitorCounts[1] != 2 {
t.Errorf("счётчики сервиторов не совпадают после загрузки: %v", loaded.ServitorCounts)
}
if loaded.Corruption != 2 || loaded.MissionsDone != 1 {
t.Errorf("скверна/завершённые миссии не совпадают после загрузки")
}
if len(loaded.Log) != 3 {
t.Errorf("журнал должен был сохраниться полностью, получено %v", loaded.Log)
}
}
func TestSaveAndLoadPreservesActiveMission(t *testing.T) {
withTempConfigDir(t)
g := NewCorpseStarchGame(csT(0))
g.TotalEarned = corpseStarchUnlockMissions
g.StartMission(csT(100), 1)
if err := SaveCorpseStarchGame(g); err != nil {
t.Fatalf("неожиданная ошибка сохранения: %v", err)
}
loaded, err := LoadCorpseStarchGame()
if err != nil {
t.Fatalf("неожиданная ошибка загрузки: %v", err)
}
if loaded.ActiveMission == nil {
t.Fatalf("активная миссия должна была сохраниться")
}
if loaded.ActiveMission.DefIdx != 1 {
t.Errorf("ожидался индекс миссии 1, получено %d", loaded.ActiveMission.DefIdx)
}
if !loaded.ActiveMission.StartedAt.Equal(csT(100)) {
t.Errorf("время начала миссии должно было сохраниться точно, получено %v", loaded.ActiveMission.StartedAt)
}
}
// TestOfflineProgressAccruesOnLoad проверяет ключевое ожидаемое
// поведение: если между сохранением и загрузкой прошло реальное
// время, пассивный доход от сервиторов должен по-честному
// начислиться при первом Tick после загрузки — ровно так же, как
// если бы игрок всё это время не отходил от экрана.
func TestOfflineProgressAccruesOnLoad(t *testing.T) {
withTempConfigDir(t)
g := NewCorpseStarchGame(csT(0))
g.ServitorCounts[0] = 10 // 10 * 0.5/сек = 5/сек
if err := SaveCorpseStarchGame(g); err != nil {
t.Fatalf("неожиданная ошибка сохранения: %v", err)
}
loaded, err := LoadCorpseStarchGame()
if err != nil {
t.Fatalf("неожиданная ошибка загрузки: %v", err)
}
loaded.Tick(csT(100))
if loaded.Starch != 500 {
t.Errorf("ожидался начисленный доход за 100 секунд отсутствия (500), получено %v", loaded.Starch)
}
}
func TestDeleteCorpseStarchSave(t *testing.T) {
withTempConfigDir(t)
g := NewCorpseStarchGame(csT(0))
if err := SaveCorpseStarchGame(g); err != nil {
t.Fatalf("неожиданная ошибка сохранения: %v", err)
}
if err := DeleteCorpseStarchSave(); err != nil {
t.Fatalf("неожиданная ошибка удаления: %v", err)
}
if _, err := LoadCorpseStarchGame(); err != ErrCorpseStarchNoSave {
t.Errorf("после удаления файла загрузка должна вернуть ErrCorpseStarchNoSave, получено: %v", err)
}
if err := DeleteCorpseStarchSave(); err != nil {
t.Errorf("повторное удаление отсутствующего файла не должно быть ошибкой, получено: %v", err)
}
}
func TestLoadCorruptedSaveFileReturnsError(t *testing.T) {
withTempConfigDir(t)
path, err := corpseStarchSavePath()
if err != nil {
t.Fatalf("неожиданная ошибка получения пути: %v", err)
}
dir := path[:len(path)-len("/corpsestarch_save.json")]
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("неожиданная ошибка создания директории: %v", err)
}
if err := os.WriteFile(path, []byte("это не json"), 0o644); err != nil {
t.Fatalf("неожиданная ошибка записи повреждённого файла: %v", err)
}
if _, err := LoadCorpseStarchGame(); err == nil {
t.Fatalf("ожидалась ошибка при загрузке повреждённого файла")
}
}
func TestSaveOverwritesPreviousSave(t *testing.T) {
withTempConfigDir(t)
g1 := NewCorpseStarchGame(csT(0))
g1.Starch = 10
SaveCorpseStarchGame(g1)
g2 := NewCorpseStarchGame(csT(0))
g2.Starch = 999
SaveCorpseStarchGame(g2)
loaded, err := LoadCorpseStarchGame()
if err != nil {
t.Fatalf("неожиданная ошибка загрузки: %v", err)
}
if loaded.Starch != 999 {
t.Errorf("повторное сохранение должно было перезаписать файл, получено %v", loaded.Starch)
}
}