Skip to content

Commit

Permalink
Merge pull request #7 from daithihearn/stats-refactor
Browse files Browse the repository at this point in the history
Stats refactor
  • Loading branch information
daithihearn authored Jan 12, 2024
2 parents dd5c9cb + c9204f8 commit b0d0fae
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 68 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ docs: #@ Generate docs
swag init -g cmd/api/main.go
.PHONY:docs
test: fmt vet #@ Run tests
go test -coverprofile=coverage-full.out ./...
go test -tags testutils -coverprofile=coverage-full.out ./...
grep -v "_mocks.go" coverage-full.out | grep -v "collection.go" > coverage.out
go tool cover -html=coverage.out -o coverage.html
.PHONY:test
fmt: #@ Format the code
go fmt ./...
vet: fmt #@ VET the code
go vet ./...
go vet -tags testutils ./...
lint: fmt #@ Run the linter
golint ./...
run: test docs vet #@ Start locally
Expand Down
5 changes: 3 additions & 2 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"cards-110-api/pkg/game"
"cards-110-api/pkg/profile"
"cards-110-api/pkg/settings"
"cards-110-api/pkg/stats"
"context"
"log"
"os"
Expand Down Expand Up @@ -92,8 +93,8 @@ func main() {
gamesColRec := db.Collection[game.Game]{Col: gameCol}
gameService := game.Service{Col: &gamesColRec}
gameHandler := game.Handler{S: &gameService}
statsService := game.StatsService{Col: &gamesColRec}
statsHandler := game.StatsHandler{S: &statsService}
statsService := stats.Service{Col: &gamesColRec}
statsHandler := stats.Handler{S: &statsService}

// Set up the API routes.
router := gin.Default()
Expand Down
7 changes: 7 additions & 0 deletions model-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Notes about the model migration from v7 to v8

## Remove _class from the model
We can just ignore this.

## Move Deck into Game
For completed games we can just delete the deck. For active games we can do this manually.
18 changes: 14 additions & 4 deletions pkg/db/collection_mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type MockCollection[T any] struct {
MockFindOneErr *[]error
MockFindResult *[][]T
MockFindErr *[]error
MockUpsertErr *[]error
}

func (m *MockCollection[T]) FindOne(ctx context.Context, filter bson.M) (T, bool, error) {
Expand Down Expand Up @@ -62,6 +63,19 @@ func (m *MockCollection[T]) Find(ctx context.Context, filter bson.M) ([]T, error
return result, err
}

func (m *MockCollection[T]) Upsert(ctx context.Context, t T, id string) error {
// Get the first element of the error array and remove it from the array, return nil if the array is empty
var err error
if len(*m.MockUpsertErr) > 0 {
err = (*m.MockUpsertErr)[0]
*m.MockUpsertErr = (*m.MockUpsertErr)[1:]
} else {
err = nil
}

return err
}

func (m *MockCollection[T]) FindOneAndUpdate(ctx context.Context, filter bson.M, update bson.M) (T, error) {
var result T
return result, nil
Expand All @@ -71,7 +85,3 @@ func (m *MockCollection[T]) FindOneAndReplace(ctx context.Context, filter bson.M
var result T
return result, nil
}

func (m *MockCollection[T]) Upsert(ctx context.Context, t T, id string) error {
return nil
}
4 changes: 3 additions & 1 deletion pkg/game/deck-utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package game

import "math/rand"
import (
"math/rand"
)

func ShuffleCards(cards []CardName) []CardName {
shuffled := make([]CardName, len(cards))
Expand Down
10 changes: 10 additions & 0 deletions pkg/game/game-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package game
import (
"cards-110-api/pkg/db"
"context"
"errors"
"go.mongodb.org/mongo-driver/bson"
"log"
)
Expand All @@ -21,6 +22,15 @@ type Service struct {
func (s *Service) Create(ctx context.Context, playerIDs []string, name string, adminID string) (Game, error) {
log.Printf("Creating new game (%s)", name)

// Check for duplicate player IDs.
uniquePlayerIDs := make(map[string]bool)
for _, id := range playerIDs {
uniquePlayerIDs[id] = true
}
if len(uniquePlayerIDs) != len(playerIDs) {
return Game{}, errors.New("duplicate player IDs")
}

// Create a new game.
game, err := NewGame(playerIDs, name, adminID)
if err != nil {
Expand Down
208 changes: 172 additions & 36 deletions pkg/game/game-service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,95 @@ package game
import (
"cards-110-api/pkg/db"
"context"
"fmt"
"errors"
"reflect"
"testing"
"time"
)

var game = Game{
ID: "1",
Name: "Test Game",
Status: ACTIVE,
Timestamp: time.Now(),
Players: []Player{
func TestCreate(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
inputPlayerIDs []string
inputName string
inputAdminID string
mockError *[]error
expectingError bool
}{
{
ID: "1",
Seat: 1,
Call: 0,
Cards: []CardName{},
Bought: 0,
Score: 0,
Rings: 0,
TeamID: "1",
Winner: false,
name: "simple create",
inputPlayerIDs: []string{"1", "2"},
inputName: "test",
inputAdminID: "1",
mockError: &[]error{nil},
},
{
ID: "2",
Seat: 2,
Call: 0,
Cards: []CardName{},
Bought: 0,
Score: 0,
Rings: 0,
TeamID: "2",
Winner: false,
name: "duplicate player IDs",
inputPlayerIDs: []string{
"1",
"1",
},
inputName: "test",
inputAdminID: "1",
mockError: &[]error{nil},
expectingError: true,
},
},
AdminID: "1",
{
name: "error thrown",
inputPlayerIDs: []string{
"1",
"2",
},
inputName: "test",
inputAdminID: "1",
mockError: &[]error{errors.New("failed to upsert")},
expectingError: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockCol := &db.MockCollection[Game]{
MockUpsertErr: test.mockError,
}

ds := &Service{
Col: mockCol,
}

result, err := ds.Create(ctx, test.inputPlayerIDs, test.name, test.inputAdminID)

if test.expectingError {
if err == nil {
t.Errorf("expected error %v, got %v", test.expectingError, err)
}
} else {
if result.Name != test.name {
t.Errorf("expected name %s, got %s", test.inputName, result.Name)
}
if result.AdminID != test.inputAdminID {
t.Errorf("expected admin id %s, got %s", test.inputAdminID, result.AdminID)
}
if len(result.Players) != len(test.inputPlayerIDs) {
t.Errorf("expected %d players, got %d", len(test.inputPlayerIDs), len(result.Players))
}
// Check that the players are in the game
for _, playerID := range test.inputPlayerIDs {
found := false
for _, player := range result.Players {
if player.ID == playerID {
found = true
break
}
}
if !found {
t.Errorf("expected player %s to be in the game", playerID)
}
}
}
})
}
}

func TestGet(t *testing.T) {
Expand All @@ -53,14 +107,34 @@ func TestGet(t *testing.T) {
expectingError bool
}{
{
name: "success",
mockResult: &[]Game{game},
name: "simple get",
mockResult: &[]Game{TwoPlayerGame()},
mockExists: &[]bool{true},
mockError: &[]error{nil},
expectedResult: game,
expectedResult: TwoPlayerGame(),
expectedExists: true,
expectingError: false,
},
{
name: "error thrown",
mockResult: &[]Game{
{},
},
mockExists: &[]bool{false},
mockError: &[]error{errors.New("something went wrong")},
expectedResult: Game{},
expectedExists: false,
expectingError: true,
},
{
name: "not found",
mockResult: &[]Game{{}},
mockExists: &[]bool{false},
mockError: &[]error{nil},
expectedResult: Game{},
expectedExists: false,
expectingError: false,
},
}

for _, test := range tests {
Expand All @@ -77,16 +151,78 @@ func TestGet(t *testing.T) {

result, exists, err := ds.Get(ctx, "1")

if fmt.Sprintf("%v", result) != fmt.Sprintf("%v", test.expectedResult) {
t.Errorf("expected result %v, got %v", test.expectedResult, result)
if test.expectingError {
if err == nil {
t.Errorf("expected error %v, got %v", test.expectingError, err)
}
} else {
if !reflect.DeepEqual(result, test.expectedResult) {
t.Errorf("expected result %v, got %v", test.expectedExists, exists)
}
}
if exists != test.expectedExists {
t.Errorf("expected exists %v, got %v", test.expectedExists, exists)
}
if (err != nil) != test.expectingError {
t.Errorf("expected error %v, got %v", test.expectingError, err)
}
})
}

}

func TestGetAll(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
mockResult *[][]Game
mockError *[]error
expectedResult []Game
expectingError bool
}{
{
name: "simple get",
mockResult: &[][]Game{{TwoPlayerGame()}},
mockError: &[]error{nil},
expectedResult: []Game{TwoPlayerGame()},
expectingError: false,
},
{
name: "error thrown",
mockResult: &[][]Game{{}},
mockError: &[]error{errors.New("something went wrong")},
expectedResult: []Game{},
expectingError: true,
},
{
name: "no results should return empty array",
mockResult: &[][]Game{},
mockError: &[]error{nil},
expectedResult: []Game{},
expectingError: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockCol := &db.MockCollection[Game]{
MockFindResult: test.mockResult,
MockFindErr: test.mockError,
}

ds := &Service{
Col: mockCol,
}

result, err := ds.GetAll(ctx)

if test.expectingError {
if err == nil {
t.Errorf("expected error %v, got %v", test.expectingError, err)
}
} else {
if !reflect.DeepEqual(result, test.expectedResult) && len(test.expectedResult) != 0 {
t.Errorf("expected result %v, got %v", test.expectedResult, result)
}
}
})
}
}
Loading

0 comments on commit b0d0fae

Please sign in to comment.