aboutsummaryrefslogtreecommitdiff
path: root/internal/player/hardening_store_sanitize_test.go
blob: 941685615332c898342864b47be3239d6cdf012c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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())
	}
}