diff options
Diffstat (limited to 'internal/player/store_test.go')
| -rw-r--r-- | internal/player/store_test.go | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/internal/player/store_test.go b/internal/player/store_test.go new file mode 100644 index 0000000..593e9d1 --- /dev/null +++ b/internal/player/store_test.go @@ -0,0 +1,81 @@ +package player + +import ( + "os" + "path/filepath" + "reflect" + "sync" + "testing" +) + +func testAccountStore(t *testing.T) *AccountStore { + t.Helper() + dir := t.TempDir() + for _, kind := range []string{"accounts", "characters"} { + if err := os.MkdirAll(filepath.Join(dir, "players", kind), 0755); err != nil { + t.Fatal(err) + } + } + return NewAccountStore(dir) +} + +func TestSaveGodPlayerDoesNotMutateLiveState(t *testing.T) { + s := testAccountStore(t) + p := New("Alice") + p.GodBackup = p.Skills + p.Skills = map[SkillName]int{Hitpoints: 1000000} + p.GodMode = true + p.HP = 999 + expected := *p + expected.Skills = p.GodBackup + wantHP := expected.MaxHP() + + // Concurrent saves of an otherwise immutable player used to race while + // temporarily swapping Skills/HP. Saving must be observationally read-only. + var wg sync.WaitGroup + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if err := s.SaveCharacter(p); err != nil { + t.Error(err) + } + }() + } + wg.Wait() + if p.HP != 999 || p.Skills[Hitpoints] != 1000000 { + t.Fatal("save changed live god-mode state") + } + saved, err := s.LoadCharacter(p.Name) + if err != nil { + t.Fatal(err) + } + if saved.HP != wantHP || !reflect.DeepEqual(saved.Skills, p.GodBackup) { + t.Fatalf("saved god-mode stats instead of underlying character: %+v", saved) + } +} + +func TestAccountStoreIgnoresNonYAMLFiles(t *testing.T) { + s := testAccountStore(t) + for _, path := range []string{s.AccountPath("Alice"), s.CharPath("Alice")} { + for _, name := range []string{"Alice.yaml", ".save-stale", "Backup.yaml.bak"} { + if err := os.WriteFile(filepath.Join(filepath.Dir(path), name), nil, 0600); err != nil { + t.Fatal(err) + } + } + } + for _, list := range []func() ([]string, error){s.ListAccountNames, s.ListCharacterNames} { + got, err := list() + if err != nil || !reflect.DeepEqual(got, []string{"Alice"}) { + t.Fatalf("list = %v, %v", got, err) + } + } + for _, find := range []func(string) (string, error){s.FindAccount, s.FindCharacter} { + if got, err := find("alice"); err != nil || got != "Alice" { + t.Fatalf("case-insensitive lookup = %q, %v", got, err) + } + if _, err := find(".save-stale"); err == nil { + t.Fatal("temporary file treated as an account/character") + } + } +} |
