diff --git a/http-server/http-server b/http-server/http-server new file mode 100755 index 0000000..7be1f43 Binary files /dev/null and b/http-server/http-server differ diff --git a/http-server/in_memory_player_store.go b/http-server/in_memory_player_store.go new file mode 100644 index 0000000..5b1034c --- /dev/null +++ b/http-server/in_memory_player_store.go @@ -0,0 +1,17 @@ +package main + +func NewInMemoryPlayerStore() *InMemoryPlayerStore { + return &InMemoryPlayerStore{map[string]int{}} +} + +type InMemoryPlayerStore struct { + store map[string]int +} + +func (i *InMemoryPlayerStore) RecordWin(name string) { + i.store[name]++ +} + +func (i *InMemoryPlayerStore) GetPlayerScore(name string) int { + return i.store[name] +} diff --git a/http-server/main.go b/http-server/main.go new file mode 100644 index 0000000..f14dabb --- /dev/null +++ b/http-server/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "log" + "net/http" +) + +func main() { + server := &PlayerServer{NewInMemoryPlayerStore()} + log.Fatal(http.ListenAndServe(":5000", server)) +} diff --git a/http-server/server.go b/http-server/server.go new file mode 100644 index 0000000..a9172e1 --- /dev/null +++ b/http-server/server.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "net/http" + "strings" +) + +type PlayerStore interface { + GetPlayerScore(name string) int + RecordWin(name string) +} + +type PlayerServer struct { + store PlayerStore +} + +func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + player := strings.TrimPrefix(r.URL.Path, "/players/") + + switch r.Method { + case http.MethodPost: + p.processWin(w, player) + case http.MethodGet: + p.showScore(w, player) + } + +} + +func (p *PlayerServer) showScore(w http.ResponseWriter, player string) { + score := p.store.GetPlayerScore(player) + + if score == 0 { + w.WriteHeader(http.StatusNotFound) + } + + fmt.Fprint(w, score) +} + +func (p *PlayerServer) processWin(w http.ResponseWriter, player string) { + p.store.RecordWin(player) + w.WriteHeader(http.StatusAccepted) +} diff --git a/http-server/server_integration_test.go b/http-server/server_integration_test.go new file mode 100644 index 0000000..86bca15 --- /dev/null +++ b/http-server/server_integration_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestRecordingWinsAndRetrievingThem(t *testing.T) { + store := NewInMemoryPlayerStore() + server := PlayerServer{store} + player := "Pepper" + + server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player)) + server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player)) + server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player)) + + response := httptest.NewRecorder() + server.ServeHTTP(response, newGetScoreRequest(player)) + assertStatus(t, response.Code, http.StatusOK) + + assertResponseBody(t, response.Body.String(), "3") +} diff --git a/http-server/server_test.go b/http-server/server_test.go new file mode 100644 index 0000000..5f2baf2 --- /dev/null +++ b/http-server/server_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +type StubPlayerStore struct { + scores map[string] int + winCalls []string +} + +func (s *StubPlayerStore) GetPlayerScore(name string) int { + score := s.scores[name] + return score +} + +func (s *StubPlayerStore) RecordWin(name string) { + s.winCalls = append(s.winCalls, name) +} + +func TestGETPlayers(t *testing.T) { + store := StubPlayerStore{ + map[string]int{ + "Pepper": 20, + "Floyd": 10, + }, + nil, + } + + server := &PlayerServer{&store} + + t.Run("Returns Pepper's score", func(t *testing.T) { + request := newGetScoreRequest("Pepper") + response := httptest.NewRecorder() + + server.ServeHTTP(response, request) + + assertStatus(t, response.Code, http.StatusOK) + assertResponseBody(t, response.Body.String(), "20") + }) + t.Run("Return Floyd's score", func(t *testing.T) { + request := newGetScoreRequest("Floyd") + response := httptest.NewRecorder() + + server.ServeHTTP(response, request) + + assertStatus(t, response.Code, http.StatusOK) + assertResponseBody(t, response.Body.String(), "10") + }) + t.Run("Return 404 on missing players", func(t *testing.T) { + request := newGetScoreRequest("Apollo") + response := httptest.NewRecorder() + + server.ServeHTTP(response, request) + + assertStatus(t, response.Code, http.StatusNotFound) + }) +} + +func TestStoreWins(t *testing.T) { + store := StubPlayerStore{ + map[string]int{}, + nil, + } + server := &PlayerServer{&store} + + t.Run("it records wins when POST", func(t *testing.T) { + player := "Pepper" + + request := newPostWinRequest(player) + response := httptest.NewRecorder() + + server.ServeHTTP(response, request) + + assertStatus(t, response.Code, http.StatusAccepted) + + if len(store.winCalls) != 1 { + t.Errorf("got %d calls to RecordWin want %d", len(store.winCalls), 1) + } + + if store.winCalls[0] != player { + t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], player) + } + }) +} + +func assertStatus(t testing.TB, got, want int) { + t.Helper() + if got != want { + t.Errorf("did not get the correct status, got %d, want %d", got, want) + } +} + +func newGetScoreRequest(name string) *http.Request { + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil) + return req +} + +func newPostWinRequest(name string) *http.Request { + req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil) + return req +} + +func assertResponseBody(t testing.TB, got, want string) { + t.Helper() + if got != want { + t.Errorf("response body is wrong, got %q, want %q", got, want) + } +}