aboutsummaryrefslogtreecommitdiff
path: root/internal/player/hardening_store_sanitize_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 05:31:09 -0400
committerhistoria <[not public]>2026-09-18 05:31:09 -0400
commit153acb7f644d755c45533b7c5311e3ffa47e8973 (patch)
tree7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/player/hardening_store_sanitize_test.go
parent87d08f0151e643951769e323995f1c5ed89ee200 (diff)
downloadthehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/player/hardening_store_sanitize_test.go')
-rw-r--r--internal/player/hardening_store_sanitize_test.go114
1 files changed, 114 insertions, 0 deletions
diff --git a/internal/player/hardening_store_sanitize_test.go b/internal/player/hardening_store_sanitize_test.go
new file mode 100644
index 0000000..9416856
--- /dev/null
+++ b/internal/player/hardening_store_sanitize_test.go
@@ -0,0 +1,114 @@
+package player
+
+// Regression: LoadCharacter must sanitize the inventory/bank slot maps. A
+// hand-edited or corrupt save with out-of-range keys, nil entries, or
+// non-positive quantities used to leave items no gameplay loop could reach
+// while still consuming capacity, and could drive FreeSlots negative.
+
+import (
+ "os"
+ "testing"
+)
+
+func TestLoadCharacterSanitizesSlotMaps(t *testing.T) {
+ s := testAccountStore(t)
+ content := `name: Corrupt
+room_id: 1
+inventory:
+ 0:
+ item_id: rope
+ quantity: 2
+ 3: null
+ 5:
+ item_id: ghost
+ quantity: 0
+ 7:
+ item_id: negative
+ quantity: -4
+ 99:
+ item_id: out_of_range
+ quantity: 1
+bank:
+ 0:
+ item_id: coins
+ quantity: 100
+ 30:
+ item_id: far_slot
+ quantity: 1
+ -2:
+ item_id: bogus
+ quantity: 3
+ 4: null
+ 6:
+ item_id: empty
+ quantity: 0
+`
+ if err := os.WriteFile(s.CharPath("Corrupt"), []byte(content), 0600); err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := s.LoadCharacter("Corrupt")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(p.Inventory) != 1 {
+ t.Fatalf("inventory = %v, want only the valid slot 0", p.Inventory)
+ }
+ slot := p.Inventory[0]
+ if slot == nil || slot.ItemID != "rope" || slot.Quantity != 2 {
+ t.Errorf("valid slot 0 changed: %+v", slot)
+ }
+ if got := p.FreeSlots(); got != 27 {
+ t.Errorf("FreeSlots() = %d, want 27", got)
+ }
+ if got := p.FreeSlots(); got < 0 {
+ t.Errorf("FreeSlots() went negative: %d", got)
+ }
+
+ if len(p.Bank) != 2 {
+ t.Fatalf("bank = %v, want only slots 0 and 30", p.Bank)
+ }
+ if bs := p.Bank[0]; bs == nil || bs.ItemID != "coins" || bs.Quantity != 100 {
+ t.Errorf("valid bank slot 0 changed: %+v", bs)
+ }
+ // Bank slots past the inventory's 28 are valid: the bank grows unbounded.
+ if bs := p.Bank[30]; bs == nil || bs.ItemID != "far_slot" {
+ t.Errorf("valid bank slot 30 dropped: %+v", bs)
+ }
+ if _, ok := p.Bank[-2]; ok {
+ t.Error("negative bank slot survived sanitization")
+ }
+}
+
+func TestSaveLoadKeepsValidSlots(t *testing.T) {
+ s := testAccountStore(t)
+ p := New("Alice")
+ p.SetInvSlot(0, &InventorySlot{ItemID: "rope", Quantity: 2, Quality: 3, MaxQuality: 5})
+ p.SetInvSlot(27, &InventorySlot{ItemID: "torch", Quantity: 1})
+ p.SetBankSlot(0, &InventorySlot{ItemID: "coins", Quantity: 100})
+ p.SetBankSlot(30, &InventorySlot{ItemID: "spade", Quantity: 1})
+ if err := s.SaveCharacter(p); err != nil {
+ t.Fatal(err)
+ }
+
+ loaded, err := s.LoadCharacter("Alice")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(loaded.Inventory) != 2 || len(loaded.Bank) != 2 {
+ t.Fatalf("slots lost on a round trip: inventory=%v bank=%v", loaded.Inventory, loaded.Bank)
+ }
+ if got := loaded.Inventory[0]; got == nil || got.ItemID != "rope" || got.Quantity != 2 || got.Quality != 3 || got.MaxQuality != 5 {
+ t.Errorf("inventory slot 0 changed: %+v", got)
+ }
+ if got := loaded.Inventory[27]; got == nil || got.ItemID != "torch" {
+ t.Errorf("inventory slot 27 changed: %+v", got)
+ }
+ if got := loaded.Bank[30]; got == nil || got.ItemID != "spade" {
+ t.Errorf("bank slot 30 changed: %+v", got)
+ }
+ if loaded.FreeSlots() != 26 {
+ t.Errorf("FreeSlots() = %d, want 26", loaded.FreeSlots())
+ }
+}