Neale Pickett
·
2023-09-29
state_test.go
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "os"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/spf13/afero"
12)
13
14func NewTestState() *State {
15 s := NewState(new(afero.MemMapFs))
16 s.refresh()
17 return s
18}
19
20func slurp(c chan bool) {
21 for range c {
22 // Nothing
23 }
24}
25
26func TestState(t *testing.T) {
27 s := NewTestState()
28 defer close(s.refreshNow)
29 go slurp(s.refreshNow)
30
31 mustExist := func(path string) {
32 _, err := s.Fs.Stat(path)
33 if os.IsNotExist(err) {
34 t.Errorf("File %s does not exist", path)
35 }
36 }
37
38 pl := s.PointsLog()
39 if len(pl) != 0 {
40 t.Errorf("Empty points log is not empty")
41 }
42
43 mustExist("initialized")
44 mustExist("hours.txt")
45
46 teamIDsBuf, err := afero.ReadFile(s.Fs, "teamids.txt")
47 if err != nil {
48 t.Errorf("Reading teamids.txt: %v", err)
49 }
50
51 teamIDs := bytes.Split(teamIDsBuf, []byte("\n"))
52 if (len(teamIDs) != 101) || (len(teamIDs[100]) > 0) {
53 t.Errorf("There weren't 100 teamIDs, there were %d", len(teamIDs))
54 }
55 teamID := string(teamIDs[0])
56
57 if _, err := s.TeamName(teamID); err == nil {
58 t.Errorf("Bad team ID lookup didn't return error")
59 }
60
61 if err := s.SetTeamName("bad team ID", "bad team name"); err == nil {
62 t.Errorf("Setting bad team ID didn't raise an error")
63 }
64
65 teamName := "My Team"
66 if err := s.SetTeamName(teamID, teamName); err != nil {
67 t.Errorf("Setting team name: %v", err)
68 }
69 if err := s.SetTeamName(teamID, "wat"); err == nil {
70 t.Errorf("Registering team a second time didn't fail")
71 }
72 s.refresh()
73 if name, err := s.TeamName(teamID); err != nil {
74 t.Error(err)
75 } else if name != teamName {
76 t.Error("Incorrect team name:", name)
77 }
78
79 category := "poot"
80 points := 3928
81 if err := s.AwardPoints(teamID, category, points); err != nil {
82 t.Error(err)
83 }
84 // Flex duplicate detection with different timestamp
85 if f, err := s.Create("points.new/moo"); err != nil {
86 t.Error("Creating duplicate points file:", err)
87 } else {
88 fmt.Fprintln(f, time.Now().Unix()+1, teamID, category, points)
89 f.Close()
90 }
91
92 s.AwardPoints(teamID, category, points)
93 s.refresh()
94 pl = s.PointsLog()
95 if len(pl) != 1 {
96 for i, award := range pl {
97 t.Logf("pl[%d] == %s", i, award.String())
98 }
99 t.Errorf("After awarding duplicate points, points log has length %d", len(pl))
100 } else if (pl[0].TeamID != teamID) || (pl[0].Category != category) || (pl[0].Points != points) {
101 t.Errorf("Incorrect logged award %v", pl)
102 }
103
104 if err := s.AwardPoints(teamID, category, points); err == nil {
105 t.Error("Duplicate points award after refresh didn't fail")
106 }
107
108 if err := s.AwardPoints(teamID, category, points+1); err != nil {
109 t.Error("Awarding more points:", err)
110 }
111
112 s.refresh()
113 if len(s.PointsLog()) != 2 {
114 t.Errorf("There should be two awards")
115 }
116
117 afero.WriteFile(s, "points.log", []byte("intentional parse error\n"), 0644)
118 s.refresh()
119 if len(s.PointsLog()) != 0 {
120 t.Errorf("Intentional parse error breaks pointslog")
121 }
122 if err := s.AwardPoints(teamID, category, points); err != nil {
123 t.Error(err)
124 }
125 s.refresh()
126 if len(s.PointsLog()) != 1 {
127 t.Log(s.PointsLog())
128 t.Error("Intentional parse error screws up all parsing")
129 }
130
131 s.Fs.Remove("initialized")
132 s.refresh()
133
134 pl = s.PointsLog()
135 if len(pl) != 0 {
136 t.Errorf("After reinitialization, points log has length %d", len(pl))
137 }
138
139}
140
141// Out of order points insertion, issue #168
142func TestStateOutOfOrderAward(t *testing.T) {
143 s := NewTestState()
144
145 category := "meow"
146 points := 100
147
148 now := time.Now().Unix()
149 if err := s.awardPointsAtTime(now+20, "AA", category, points); err != nil {
150 t.Error("Awarding points to team ZZ:", err)
151 }
152 if err := s.awardPointsAtTime(now+10, "ZZ", category, points); err != nil {
153 t.Error("Awarding points to team AA:", err)
154 }
155 s.refresh()
156 pl := s.PointsLog()
157 if len(pl) != 2 {
158 t.Error("Wrong length for points log")
159 }
160 if pl[0].TeamID != "ZZ" {
161 t.Error("Out of order points insertion not properly sorted in points log")
162 }
163}
164
165func TestStateEvents(t *testing.T) {
166 s := NewTestState()
167 s.LogEvent("moo", "", "", 0)
168 s.LogEvent("moo 2", "", "", 0)
169
170 if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "init:::0" {
171 t.Error("Wrong message from event stream:", msg)
172 }
173 if msg := <-s.eventStream; !strings.HasPrefix(msg[5], "state/hours.txt") {
174 t.Error("Wrong message from event stream:", msg[5])
175 }
176 if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo:::0" {
177 t.Error("Wrong message from event stream:", msg)
178 }
179 if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo 2:::0" {
180 t.Error("Wrong message from event stream:", msg)
181 }
182}
183
184func TestStateDisabled(t *testing.T) {
185 s := NewTestState()
186 s.refresh()
187
188 if !s.Enabled() {
189 t.Error("Brand new state is disabled")
190 }
191
192 hoursFile, err := s.Create("hours.txt")
193 if err != nil {
194 t.Error(err)
195 }
196 defer hoursFile.Close()
197 s.refresh()
198 if !s.Enabled() {
199 t.Error("Empty hours.txt not enabled")
200 }
201
202 fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z")
203 hoursFile.Sync()
204 s.refresh()
205 if s.Enabled() {
206 t.Error("1970-01-01")
207 }
208
209 fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00")
210 hoursFile.Sync()
211 s.refresh()
212 if !s.Enabled() {
213 t.Error("1970-01-02")
214 }
215
216 fmt.Fprintln(hoursFile, "-")
217 hoursFile.Sync()
218 s.refresh()
219 if s.Enabled() {
220 t.Error("bare -")
221 }
222
223 fmt.Fprintln(hoursFile, "+")
224 hoursFile.Sync()
225 s.refresh()
226 if !s.Enabled() {
227 t.Error("bare +")
228 }
229
230 fmt.Fprintln(hoursFile, "")
231 fmt.Fprintln(hoursFile, "# Comment")
232 hoursFile.Sync()
233 s.refresh()
234 if !s.Enabled() {
235 t.Error("Comment")
236 }
237
238 fmt.Fprintln(hoursFile, "intentional parse error")
239 hoursFile.Sync()
240 s.refresh()
241 if !s.Enabled() {
242 t.Error("intentional parse error")
243 }
244
245 fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z")
246 hoursFile.Sync()
247 s.refresh()
248 if s.Enabled() {
249 t.Error("1980-01-01")
250 }
251
252 if err := s.Remove("hours.txt"); err != nil {
253 t.Error(err)
254 }
255 s.refresh()
256 if !s.Enabled() {
257 t.Error("Removing `hours.txt` disabled event")
258 }
259
260 s.Remove("initialized")
261 s.refresh()
262 if !s.Enabled() {
263 t.Error("Re-initializing didn't start event")
264 }
265}
266
267func TestStateMaintainer(t *testing.T) {
268 updateInterval := 10 * time.Millisecond
269
270 s := NewTestState()
271 go s.Maintain(updateInterval)
272
273 if _, err := s.Stat("initialized"); err != nil {
274 t.Error(err)
275 }
276 teamIDLines, err := afero.ReadFile(s, "teamids.txt")
277 if err != nil {
278 t.Error(err)
279 }
280 teamIDList := strings.Split(string(teamIDLines), "\n")
281 if len(teamIDList) != 101 {
282 t.Error("TeamIDList length is", len(teamIDList))
283 }
284 teamID := teamIDList[0]
285 if len(teamID) < 6 {
286 t.Error("Team ID too short:", teamID)
287 }
288
289 s.LogEvent("Hello!", "", "", 0)
290
291 if len(s.PointsLog()) != 0 {
292 t.Error("Points log is not empty")
293 }
294 if err := s.SetTeamName(teamID, "The Patricks"); err != nil {
295 t.Error(err)
296 }
297 if err := s.AwardPoints(teamID, "pategory", 31337); err != nil {
298 t.Error(err)
299 }
300 time.Sleep(updateInterval)
301 pl := s.PointsLog()
302 if len(pl) != 1 {
303 t.Error("Points log should have one entry")
304 }
305 if (pl[0].Category != "pategory") || (pl[0].TeamID != teamID) {
306 t.Error("Wrong points event was recorded")
307 }
308
309 time.Sleep(updateInterval)
310
311 eventLog, err := afero.ReadFile(s.Fs, "events.csv")
312 if err != nil {
313 t.Error(err)
314 } else if events := strings.Split(string(eventLog), "\n"); len(events) != 4 {
315 t.Log("Events:", events)
316 t.Error("Wrong event log length:", len(events))
317 } else if events[3] != "" {
318 t.Error("Event log didn't end with newline", events)
319 }
320}
321
322func TestDevelState(t *testing.T) {
323 s := NewTestState()
324 ds := NewDevelState(s)
325 if err := ds.SetTeamName("boog", "The Boog Team"); err != ErrAlreadyRegistered {
326 t.Error("Registering a team that doesn't exist", err)
327 } else if err == nil {
328 t.Error("Registering a team that doesn't exist didn't return ErrAlreadyRegistered")
329 }
330 if n, err := ds.TeamName("boog"); err != nil {
331 t.Error("Devel State returned error on team name lookup")
332 } else if n != "«devel:boog»" {
333 t.Error("Wrong team name", n)
334 }
335
336 if err := ds.AwardPoints("blerg", "dog", 82); err != nil {
337 t.Error("Devel State AwardPoints returned an error", err)
338 }
339}