Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stats refactor #7

Merged
merged 5 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading