Improve server scaffolding

This commit is contained in:
Andrea Fazzi 2022-05-25 18:09:54 +02:00
parent 8af9a6813c
commit d4aa3a7a80
6 changed files with 103 additions and 10 deletions

View file

@ -8,6 +8,6 @@ import (
) )
func main() { func main() {
server := &PlayerServer{&store.InMemoryPlayerStore{}} server := NewPlayerServer(&store.InMemoryPlayerStore{})
log.Fatal(http.ListenAndServe(":3000", server)) log.Fatal(http.ListenAndServe(":3000", server))
} }

View file

@ -1,10 +1,15 @@
package main package main
import ( import (
"encoding/json"
"fmt"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"testing" "testing"
"git.andreafazzi.eu/andrea/testhub/store"
"github.com/remogatto/prettytest" "github.com/remogatto/prettytest"
) )
@ -16,6 +21,7 @@ type testSuite struct {
type StubPlayerStore struct { type StubPlayerStore struct {
scores map[string]int scores map[string]int
winCalls []string winCalls []string
league []store.Player
} }
func (store *StubPlayerStore) GetPlayerScore(player string) int { func (store *StubPlayerStore) GetPlayerScore(player string) int {
@ -26,6 +32,10 @@ func (s *StubPlayerStore) RecordWin(name string) {
s.winCalls = append(s.winCalls, name) s.winCalls = append(s.winCalls, name)
} }
func (store *StubPlayerStore) GetLeague() []store.Player {
return store.league
}
func TestRunner(t *testing.T) { func TestRunner(t *testing.T) {
prettytest.Run( prettytest.Run(
t, t,
@ -33,7 +43,7 @@ func TestRunner(t *testing.T) {
) )
} }
func (t *testSuite) TestGET() { func (t *testSuite) TestGETPlayers() {
tests := []struct { tests := []struct {
name string name string
expectedScore string expectedScore string
@ -50,9 +60,10 @@ func (t *testSuite) TestGET() {
"Floyd": 10, "Floyd": 10,
}, },
nil, nil,
[]store.Player{},
} }
server := &PlayerServer{store} server := NewPlayerServer(store)
for _, test := range tests { for _, test := range tests {
request, _ := http.NewRequest(http.MethodGet, "/players/"+test.name, nil) request, _ := http.NewRequest(http.MethodGet, "/players/"+test.name, nil)
@ -65,13 +76,14 @@ func (t *testSuite) TestGET() {
} }
func (t *testSuite) TestPOST() { func (t *testSuite) TestPOSTPlayers() {
store := StubPlayerStore{ store := &StubPlayerStore{
map[string]int{}, map[string]int{},
nil, nil,
[]store.Player{},
} }
server := &PlayerServer{&store} server := NewPlayerServer(store)
request, _ := http.NewRequest(http.MethodPost, "/players/Pepper", nil) request, _ := http.NewRequest(http.MethodPost, "/players/Pepper", nil)
response := httptest.NewRecorder() response := httptest.NewRecorder()
@ -80,3 +92,42 @@ func (t *testSuite) TestPOST() {
t.Equal(1, len(store.winCalls)) t.Equal(1, len(store.winCalls))
t.Equal(http.StatusAccepted, response.Code) 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)
}

6
models/player.go Normal file
View file

@ -0,0 +1,6 @@
package models
type Player struct {
Name string
Wins int
}

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -8,16 +9,33 @@ import (
"git.andreafazzi.eu/andrea/testhub/store" "git.andreafazzi.eu/andrea/testhub/store"
) )
const jsonContentType = "application/json"
type PlayerServer struct { type PlayerServer struct {
store store.PlayerStore store store.PlayerStore
http.Handler
} }
func (ps *PlayerServer) processWin(w http.ResponseWriter, player string) { func NewPlayerServer(store store.PlayerStore) *PlayerServer {
ps.store.RecordWin(player) ps := new(PlayerServer)
w.WriteHeader(http.StatusAccepted) 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/") player := strings.TrimPrefix(r.URL.Path, "/players/")
switch r.Method { switch r.Method {
@ -31,3 +49,8 @@ func (ps *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ps.processWin(w, player) ps.processWin(w, player)
} }
} }
func (ps *PlayerServer) processWin(w http.ResponseWriter, player string) {
ps.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
}

View file

@ -1,5 +1,9 @@
package store package store
import (
"git.andreafazzi.eu/andrea/testhub/models"
)
type InMemoryPlayerStore struct{} type InMemoryPlayerStore struct{}
func (i *InMemoryPlayerStore) GetPlayerScore(player string) int { func (i *InMemoryPlayerStore) GetPlayerScore(player string) int {
@ -9,3 +13,7 @@ func (i *InMemoryPlayerStore) GetPlayerScore(player string) int {
func (i *InMemoryPlayerStore) RecordWin(player string) { func (i *InMemoryPlayerStore) RecordWin(player string) {
return return
} }
func (i *InMemoryPlayerStore) GetLeague() []models.Player {
return []models.Player{}
}

View file

@ -1,6 +1,11 @@
package store package store
import (
"git.andreafazzi.eu/andrea/testhub/models"
)
type PlayerStore interface { type PlayerStore interface {
GetPlayerScore(player string) int GetPlayerScore(player string) int
RecordWin(player string) RecordWin(player string)
GetLeague() []models.Player
} }