package game // Regression: character/account liveness must be matched case-insensitively, // like the game's own membership check (isMemberOf). The admin raw file editor // passes a file NAME ("players/characters/ALICE.yaml"), and an account file's // character list is hand-editable, so an exact-match lookup both let an online // character's file be overwritten and let an online character's account be // purged. import ( "strings" "testing" "thehouseoficarus/internal/net" ) func TestAdminPlayerFileOnlineMatchesNamesCaseInsensitively(t *testing.T) { h := newHarness(t) h.seedAccount("alice") h.seedCharacter("alice", "alice") h.seedAccount("carol") h.seedCharacter("carol", "carol") // The account file spells its character list differently from the live // session's character name. acc, err := h.g.AccountStore.LoadAccount("alice") if err != nil { t.Fatalf("load account: %v", err) } acc.Characters = []string{"ALICE"} if err := h.g.AccountStore.SaveAccount(acc); err != nil { t.Fatalf("save account: %v", err) } h.connect("alice") cases := []struct { name string path string want bool }{ {"online character, exact case", "players/characters/alice.yaml", true}, {"online character, upper case", "players/characters/ALICE.yaml", true}, {"online character, mixed case", "players/characters/Alice.yaml", true}, {"account of an online character, list case differs", "players/accounts/alice.yaml", true}, {"offline character, upper case", "players/characters/CAROL.yaml", false}, {"offline account", "players/accounts/carol.yaml", false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := h.g.AdminPlayerFileOnline(tc.path); got != tc.want { t.Fatalf("AdminPlayerFileOnline(%q) = %v, want %v", tc.path, got, tc.want) } }) } } func TestPurgeRefusesCharacterListedInAnotherCase(t *testing.T) { h := newHarness(t) h.seedAccount("alice") h.seedCharacter("alice", "alice") acc, err := h.g.AccountStore.LoadAccount("alice") if err != nil { t.Fatalf("load account: %v", err) } acc.Characters = []string{"ALICE"} if err := h.g.AccountStore.SaveAccount(acc); err != nil { t.Fatalf("save account: %v", err) } h.connect("alice") conn := newChanConn() menu := &net.Session{Conn: conn, Account: acc} menu.SetState(net.StateMenu) h.g.RunOnOwner(func() { h.g.handlePurgeAccount(menu, "PURGE alice") }) if out := string(conn.Bytes()); !strings.Contains(out, "is logged in") { t.Fatalf("purge was not refused for a live character: %q", out) } if _, err := h.g.AccountStore.LoadAccount("alice"); err != nil { t.Fatalf("account was purged while its character was online: %v", err) } }