aboutsummaryrefslogtreecommitdiff
path: root/internal/player/roomset_test.go
blob: 94b17148760decf8ff8a7826f0170a7683895f8a (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package player

import (
	"encoding/base64"
	"encoding/binary"
	"strings"
	"testing"

	"gopkg.in/yaml.v3"
)

func TestRoomSetYAMLRoundTrip(t *testing.T) {
	stats := PlayerStats{
		RoomsVisited: NewRoomSetFrom(1, 5, 300),
		MobKills:     map[string]int{"rat": 3},
		TotalKills:   3,
		Deaths:       0,
	}
	data, err := yaml.Marshal(stats)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	out := string(data)
	if !strings.Contains(out, "rooms_visited:") {
		t.Fatalf("expected rooms_visited key in output:\n%s", out)
	}

	var loaded PlayerStats
	if err := yaml.Unmarshal(data, &loaded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if loaded.RoomsVisited.Len() != 3 {
		t.Errorf("Len = %d, want 3", loaded.RoomsVisited.Len())
	}
	for _, id := range []int{1, 5, 300} {
		if !loaded.RoomsVisited.Has(id) {
			t.Errorf("expected Has(%d) == true", id)
		}
	}
	if loaded.RoomsVisited.Has(2) {
		t.Error("expected Has(2) == false")
	}
}

func TestRoomSetOmitEmpty(t *testing.T) {
	stats := PlayerStats{}
	data, err := yaml.Marshal(stats)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	out := string(data)
	if strings.Contains(out, "rooms_visited:") {
		t.Errorf("expected rooms_visited to be absent for empty set:\n%s", out)
	}

	var loaded PlayerStats
	if err := yaml.Unmarshal(data, &loaded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if loaded.RoomsVisited.Len() != 0 {
		t.Errorf("Len = %d, want 0", loaded.RoomsVisited.Len())
	}
}

func TestRoomSetIsZero(t *testing.T) {
	var zero RoomSet
	if !zero.IsZero() {
		t.Error("zero value should be IsZero")
	}
	visited := NewRoomSetFrom(1)
	if visited.IsZero() {
		t.Error("non-empty set should not be IsZero")
	}
}

func TestRoomSetMarshalEmpty(t *testing.T) {
	var rs RoomSet
	v, err := rs.MarshalYAML()
	if err != nil {
		t.Fatalf("MarshalYAML: %v", err)
	}
	s, ok := v.(string)
	if !ok {
		t.Fatalf("MarshalYAML returned %T, want string", v)
	}
	if s != "" {
		t.Errorf("MarshalYAML empty = %q, want empty string", s)
	}
}

// Sparse, high room IDs are the reason RoomSet is not a dense bitset: the
// encoding must stay proportional to the visited count, not the largest ID.
func TestRoomSetSparseEncodingIsCompact(t *testing.T) {
	var rs RoomSet
	for _, id := range []int{1001, 1002, 1003, 2001, 2006, 99999301} {
		rs.Add(id)
	}
	data, err := yaml.Marshal(PlayerStats{RoomsVisited: rs})
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	if len(data) > 200 {
		t.Fatalf("encoding is not compact for sparse IDs: %d bytes\n%s", len(data), data)
	}

	var loaded PlayerStats
	if err := yaml.Unmarshal(data, &loaded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	for _, id := range []int{1001, 1002, 1003, 2001, 2006, 99999301} {
		if !loaded.RoomsVisited.Has(id) {
			t.Errorf("round trip lost room %d", id)
		}
	}
	if loaded.RoomsVisited.Has(5000) {
		t.Error("round trip invented room 5000")
	}
}

// The prototype has no legacy formats: anything without the v1 prefix is a
// hard error rather than a silently-decoded old bitset.
func TestRoomSetRejectsUnknownEncoding(t *testing.T) {
	var wrap struct {
		Visited RoomSet `yaml:"rooms_visited"`
	}
	if err := yaml.Unmarshal([]byte("rooms_visited: AQIDBA==\n"), &wrap); err == nil {
		t.Fatal("expected error for unprefixed encoding")
	}
	if err := yaml.Unmarshal([]byte("rooms_visited: [7, 42, 900]\n"), &wrap); err == nil {
		t.Fatal("expected error for YAML sequence encoding")
	}
}

func TestRoomSetRejectsOutOfRangeDelta(t *testing.T) {
	// A valid uvarint delta above roomSetMaxID must fail rather than wrap or
	// synthesize an absurd room ID.
	buf := binary.AppendUvarint(nil, uint64(roomSetMaxID)+1)
	encoded := roomSetV1Prefix + base64.StdEncoding.EncodeToString(buf)
	var wrap struct {
		Visited RoomSet `yaml:"rooms_visited"`
	}
	if err := yaml.Unmarshal([]byte("rooms_visited: "+encoded+"\n"), &wrap); err == nil {
		t.Fatal("expected error for out-of-range delta")
	}
}

func TestRoomSetAddRejectsOutOfRange(t *testing.T) {
	rs := NewRoomSetFrom(roomSetMaxID)
	if !rs.Has(roomSetMaxID) {
		t.Fatal("roomSetMaxID itself should be accepted")
	}
	rs.Add(roomSetMaxID + 1)
	if rs.Has(roomSetMaxID + 1) {
		t.Fatal("out-of-range room ID was accepted")
	}
}