From d4aa3a7a8049075ec537a264f6d76f9406c4d401 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Wed, 25 May 2022 18:09:54 +0200 Subject: [PATCH] Improve server scaffolding --- main.go | 2 +- main_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++---- models/player.go | 6 +++++ server.go | 31 ++++++++++++++++++++---- store/memory.go | 8 +++++++ store/store.go | 5 ++++ 6 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 models/player.go diff --git a/main.go b/main.go index 5e34774..5d2962c 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,6 @@ import ( ) func main() { - server := &PlayerServer{&store.InMemoryPlayerStore{}} + server := NewPlayerServer(&store.InMemoryPlayerStore{}) log.Fatal(http.ListenAndServe(":3000", server)) } diff --git a/main_test.go b/main_test.go index 1942236..398429c 100644 --- a/main_test.go +++ b/main_test.go @@ -1,10 +1,15 @@ package main import ( + "encoding/json" + "fmt" + "io" "net/http" "net/http/httptest" + "reflect" "testing" + "git.andreafazzi.eu/andrea/testhub/store" "github.com/remogatto/prettytest" ) @@ -16,6 +21,7 @@ type testSuite struct { type StubPlayerStore struct { scores map[string]int winCalls []string + league []store.Player } func (store *StubPlayerStore) GetPlayerScore(player string) int { @@ -26,6 +32,10 @@ func (s *StubPlayerStore) RecordWin(name string) { s.winCalls = append(s.winCalls, name) } +func (store *StubPlayerStore) GetLeague() []store.Player { + return store.league +} + func TestRunner(t *testing.T) { prettytest.Run( t, @@ -33,7 +43,7 @@ func TestRunner(t *testing.T) { ) } -func (t *testSuite) TestGET() { +func (t *testSuite) TestGETPlayers() { tests := []struct { name string expectedScore string @@ -50,9 +60,10 @@ func (t *testSuite) TestGET() { "Floyd": 10, }, nil, + []store.Player{}, } - server := &PlayerServer{store} + server := NewPlayerServer(store) for _, test := range tests { request, _ := http.NewRequest(http.MethodGet, "/players/"+test.name, nil) @@ -65,13 +76,14 @@ func (t *testSuite) TestGET() { } -func (t *testSuite) TestPOST() { - store := StubPlayerStore{ +func (t *testSuite) TestPOSTPlayers() { + store := &StubPlayerStore{ map[string]int{}, nil, + []store.Player{}, } - server := &PlayerServer{&store} + server := NewPlayerServer(store) request, _ := http.NewRequest(http.MethodPost, "/players/Pepper", nil) response := httptest.NewRecorder() @@ -80,3 +92,42 @@ func (t *testSuite) TestPOST() { t.Equal(1, len(store.winCalls)) t.Equal(http.StatusAccepted, response.Code) } + +func (t *testSuite) TestGETLeague() { + expectedResult := []store.Player{ + {Name: "Cleo", Wins: 20}, + {Name: "Chris", Wins: 10}, + } + + store := &StubPlayerStore{ + nil, + nil, + expectedResult, + } + + server := NewPlayerServer(store) + + request, _ := http.NewRequest(http.MethodGet, "/league", nil) + response := httptest.NewRecorder() + + server.ServeHTTP(response, request) + + result := getLeagueFromResponse(response.Body) + + t.True(leaguesAreEqual(expectedResult, result)) + t.Equal(http.StatusOK, response.Code) +} + +func getLeagueFromResponse(body io.Reader) (league []store.Player) { + err := json.NewDecoder(body).Decode(&league) + + if err != nil { + panic(fmt.Errorf("Unable to parse response from server %q into slice of Player, '%v'", body, err)) + } + + return +} + +func leaguesAreEqual(got, want []store.Player) bool { + return reflect.DeepEqual(got, want) +} diff --git a/models/player.go b/models/player.go new file mode 100644 index 0000000..fffb3be --- /dev/null +++ b/models/player.go @@ -0,0 +1,6 @@ +package models + +type Player struct { + Name string + Wins int +} diff --git a/server.go b/server.go index 9241bbc..380b741 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "net/http" "strings" @@ -8,16 +9,33 @@ import ( "git.andreafazzi.eu/andrea/testhub/store" ) +const jsonContentType = "application/json" + type PlayerServer struct { store store.PlayerStore + http.Handler } -func (ps *PlayerServer) processWin(w http.ResponseWriter, player string) { - ps.store.RecordWin(player) - w.WriteHeader(http.StatusAccepted) +func NewPlayerServer(store store.PlayerStore) *PlayerServer { + ps := new(PlayerServer) + ps.store = store + + router := http.NewServeMux() + + router.Handle("/players/", http.HandlerFunc(ps.playersHandler)) + router.Handle("/league", http.HandlerFunc(ps.leagueHandler)) + + ps.Handler = router + + return ps } -func (ps *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (ps *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("content-type", jsonContentType) + json.NewEncoder(w).Encode(ps.store.GetLeague()) +} + +func (ps *PlayerServer) playersHandler(w http.ResponseWriter, r *http.Request) { player := strings.TrimPrefix(r.URL.Path, "/players/") switch r.Method { @@ -31,3 +49,8 @@ func (ps *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { ps.processWin(w, player) } } + +func (ps *PlayerServer) processWin(w http.ResponseWriter, player string) { + ps.store.RecordWin(player) + w.WriteHeader(http.StatusAccepted) +} diff --git a/store/memory.go b/store/memory.go index e622106..7135408 100644 --- a/store/memory.go +++ b/store/memory.go @@ -1,5 +1,9 @@ package store +import ( + "git.andreafazzi.eu/andrea/testhub/models" +) + type InMemoryPlayerStore struct{} func (i *InMemoryPlayerStore) GetPlayerScore(player string) int { @@ -9,3 +13,7 @@ func (i *InMemoryPlayerStore) GetPlayerScore(player string) int { func (i *InMemoryPlayerStore) RecordWin(player string) { return } + +func (i *InMemoryPlayerStore) GetLeague() []models.Player { + return []models.Player{} +} diff --git a/store/store.go b/store/store.go index f4f931c..d3f254b 100644 --- a/store/store.go +++ b/store/store.go @@ -1,6 +1,11 @@ package store +import ( + "git.andreafazzi.eu/andrea/testhub/models" +) + type PlayerStore interface { GetPlayerScore(player string) int RecordWin(player string) + GetLeague() []models.Player }