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
157
|
package main
import (
"bytes"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
)
type nullLogger struct{}
func (nullLogger) Printf(string, ...interface{}) {}
////////////////////////////////////////////////////////////////////////////////
// Test scoreboard component
type fileStub struct {
*bytes.Buffer
}
func newFileStub(init string) *fileStub {
return &fileStub{Buffer: bytes.NewBufferString(init)}
}
func (fs *fileStub) Truncate(i int64) error {
fs.Buffer.Truncate(int(i))
return nil
}
func (fs *fileStub) Seek(i int64, whence int) (int64, error) {
return i, nil
}
func TestScoreboard(t *testing.T) {
newScoreboard := func(t *testing.T, fileStub *fileStub, saveTicker <-chan time.Time) *scoreboard {
t.Helper()
scoreboard, err := newScoreboard(fileStub, saveTicker, nullLogger{})
if err != nil {
t.Errorf("unexpected error checking saved scored: %v", err)
}
return scoreboard
}
assertScores := func(t *testing.T, expScores, gotScores map[string]int) {
t.Helper()
if !reflect.DeepEqual(expScores, gotScores) {
t.Errorf("expected scores of %+v, but instead got %+v", expScores, gotScores)
}
}
assertSavedScores := func(t *testing.T, expScores map[string]int, fileStub *fileStub) {
t.Helper()
fileStubCp := newFileStub(fileStub.String())
tmpScoreboard := newScoreboard(t, fileStubCp, nil)
assertScores(t, expScores, tmpScoreboard.scores())
}
t.Run("loading", func(t *testing.T) {
// make sure loading scoreboards with various file contents works
assertSavedScores(t, map[string]int{}, newFileStub(""))
assertSavedScores(t, map[string]int{"foo": 1}, newFileStub(`{"foo":1}`))
assertSavedScores(t, map[string]int{"foo": 1, "bar": -2}, newFileStub(`{"foo":1,"bar":-2}`))
})
t.Run("tracking", func(t *testing.T) {
scoreboard := newScoreboard(t, newFileStub(""), nil)
assertScores(t, map[string]int{}, scoreboard.scores()) // sanity check
scoreboard.guessedCorrect("foo")
assertScores(t, map[string]int{"foo": 1000}, scoreboard.scores())
scoreboard.guessedIncorrect("bar")
assertScores(t, map[string]int{"foo": 1000, "bar": -1}, scoreboard.scores())
scoreboard.guessedIncorrect("foo")
assertScores(t, map[string]int{"foo": 999, "bar": -1}, scoreboard.scores())
})
t.Run("saving", func(t *testing.T) {
// this test tests scoreboard's periodic save feature using a ticker
// channel which will be written to manually. The saveLoopWaitCh is used
// here to ensure that each ticker has been fully processed.
ticker := make(chan time.Time)
fileStub := newFileStub("")
scoreboard := newScoreboard(t, fileStub, ticker)
tick := func() {
ticker <- time.Time{}
scoreboard.saveLoopWaitCh <- struct{}{}
}
// this should not effect the save file at first
scoreboard.guessedCorrect("foo")
assertSavedScores(t, map[string]int{}, fileStub)
// after the ticker the new score should get saved
tick()
assertSavedScores(t, map[string]int{"foo": 1000}, fileStub)
// ticker again after no changes should save the same thing as before
tick()
assertSavedScores(t, map[string]int{"foo": 1000}, fileStub)
// buffer a bunch of changes, shouldn't get saved till after tick
scoreboard.guessedCorrect("foo")
scoreboard.guessedCorrect("bar")
scoreboard.guessedCorrect("bar")
assertSavedScores(t, map[string]int{"foo": 1000}, fileStub)
tick()
assertSavedScores(t, map[string]int{"foo": 2000, "bar": 2000}, fileStub)
})
}
////////////////////////////////////////////////////////////////////////////////
// Test httpHandler component
type mockScoreboard map[string]int
func (mockScoreboard) guessedCorrect(name string) int { return 1 }
func (mockScoreboard) guessedIncorrect(name string) int { return -1 }
func (m mockScoreboard) scores() map[string]int { return m }
type mockRandSrc struct{}
func (m mockRandSrc) Int() int { return 666 }
func TestHTTPHandlers(t *testing.T) {
mockScoreboard := mockScoreboard{"foo": 1, "bar": 2}
httpHandlers := newHTTPHandlers(mockScoreboard, mockRandSrc{}, nullLogger{})
assertRequest := func(t *testing.T, expCode int, expBody string, r *http.Request) {
t.Helper()
rw := httptest.NewRecorder()
httpHandlers.ServeHTTP(rw, r)
if rw.Code != expCode {
t.Errorf("expected HTTP response code %d, got %d", expCode, rw.Code)
} else if rw.Body.String() != expBody {
t.Errorf("expected HTTP response body %q, got %q", expBody, rw.Body.String())
}
}
r := httptest.NewRequest("GET", "/guess?name=foo&n=665", nil)
assertRequest(t, 400, "Try higher. Your score is now -1\n", r)
r = httptest.NewRequest("GET", "/guess?name=foo&n=667", nil)
assertRequest(t, 400, "Try lower. Your score is now -1\n", r)
r = httptest.NewRequest("GET", "/guess?name=foo&n=666", nil)
assertRequest(t, 200, "Correct! Your score is now 1\n", r)
r = httptest.NewRequest("GET", "/scores", nil)
assertRequest(t, 200, "bar: 2\nfoo: 1\n", r)
}
|