diff --git a/Makefile b/Makefile index 2cff85b..70191cd 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PROJECT=github.com/takama/backer # Use the 0.0.0 tag for testing, it shouldn't clobber any release builds -RELEASE?=0.0.2 +RELEASE?=0.1.0 BUILDTAGS= diff --git a/README.md b/README.md index 6c0f9e5..6393ad8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Backer +[![Build Status](https://travis-ci.org/takama/backer.svg?branch=master)](https://travis-ci.org/takama/backer) +[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/takama/backer/issues) +[![Go Report Card](https://goreportcard.com/badge/github.com/takama/backer)](https://goreportcard.com/report/github.com/takama/backer) + Backer service which is allowed players to back each other and get a part the prize in case of a win. ## Logic description diff --git a/db/transact.go b/db/transact.go new file mode 100644 index 0000000..c2461d7 --- /dev/null +++ b/db/transact.go @@ -0,0 +1,7 @@ +package db + +// Transact contains transaction control methods +type Transact interface { + Commit() error + Rollback() error +} diff --git a/player/controller.go b/player/controller.go new file mode 100644 index 0000000..4823f2f --- /dev/null +++ b/player/controller.go @@ -0,0 +1,14 @@ +package player + +import ( + "github.com/takama/backer/db" + "github.com/takama/backer/model" +) + +// Controller defines DB interface for Player Entry +type Controller interface { + Transaction() (db.Transact, error) + NewPlayer(ID string, tx db.Transact) error + FindPlayer(ID string, tx db.Transact) (*model.Player, error) + SavePlayer(player *model.Player, tx db.Transact) error +} diff --git a/player/player.go b/player/player.go new file mode 100644 index 0000000..053060b --- /dev/null +++ b/player/player.go @@ -0,0 +1,141 @@ +package player + +import ( + "errors" + "sync" + + "github.com/takama/backer" + "github.com/takama/backer/helper" + "github.com/takama/backer/model" +) + +var ( + // ErrInsufficientPoints appears if player has not enough points + ErrInsufficientPoints = errors.New("Insufficient points") +) + +// Entry implements Player interface +type Entry struct { + Controller + mutex sync.RWMutex + model.Player +} + +// New returns new Entry which implement Player interface +func New(id string, ctrl Controller) (*Entry, error) { + tx, err := ctrl.Transaction() + if err != nil { + return nil, err + } + + entry := &Entry{Controller: ctrl} + + player, err := ctrl.FindPlayer(id, tx) + if err != nil { + err = ctrl.NewPlayer(id, tx) + if err != nil { + tx.Rollback() + return nil, err + } + player = &model.Player{ID: id} + } + entry.Player = *player + + err = tx.Commit() + if err != nil { + return nil, err + } + + return entry, nil +} + +// Fund funds (add to balance) player with amount +func (entry *Entry) Fund(amount backer.Points) error { + tx, err := entry.Controller.Transaction() + if err != nil { + return err + } + + player, err := entry.Controller.FindPlayer(entry.Player.ID, tx) + if err != nil { + tx.Rollback() + return err + } + + player.Balance = backer.Points( + helper.RoundPrice(float32(player.Balance) + helper.TruncatePrice(float32(amount)))) + err = entry.Controller.SavePlayer(player, tx) + if err != nil { + tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + entry.Player.Balance = player.Balance + + return nil +} + +// Take takes points from player account +func (entry *Entry) Take(amount backer.Points) error { + tx, err := entry.Controller.Transaction() + if err != nil { + return err + } + + player, err := entry.Controller.FindPlayer(entry.Player.ID, tx) + if err != nil { + tx.Rollback() + return err + } + + if player.Balance < amount { + return ErrInsufficientPoints + } + + player.Balance = backer.Points( + helper.RoundPrice(float32(player.Balance) - helper.TruncatePrice(float32(amount)))) + err = entry.Controller.SavePlayer(player, tx) + if err != nil { + tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + entry.Player.Balance = player.Balance + + return nil +} + +// Balance gets current points +func (entry *Entry) Balance() (backer.Points, error) { + player, err := entry.Controller.FindPlayer(entry.Player.ID, nil) + if err != nil { + return 0, err + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + entry.Player.Balance = player.Balance + + return player.Balance, nil +} + +// ID returns player ID +func (entry *Entry) ID() string { + entry.mutex.RLock() + defer entry.mutex.RUnlock() + return entry.Player.ID +} diff --git a/player/player_test.go b/player/player_test.go new file mode 100644 index 0000000..5b7b767 --- /dev/null +++ b/player/player_test.go @@ -0,0 +1,220 @@ +package player + +import ( + "errors" + "sync" + "testing" + + "github.com/takama/backer/db" + "github.com/takama/backer/model" +) + +var ( + ErrFalseTransaction = errors.New("Test false transaction") + ErrFalseCommit = errors.New("Test false commit") + ErrFalseRollback = errors.New("Test false rollback") + ErrNewPlayer = errors.New("Test new player with error") + ErrFindPlayer = errors.New("Test find player with error") + ErrSavePlayer = errors.New("Test save player with error") + ErrAlreadyExist = errors.New("Record already exists") + ErrNotExist = errors.New("Record does not exist") +) + +func test(t *testing.T, expected bool, messages ...interface{}) { + if !expected { + t.Error(messages) + } +} + +type playerTxSuccess struct{} + +func (ptx playerTxSuccess) Commit() error { + return nil +} + +func (ptx playerTxSuccess) Rollback() error { + return nil +} + +type playerTxFalse struct{} + +func (ptx playerTxFalse) Commit() error { + return ErrFalseCommit +} + +func (ptx playerTxFalse) Rollback() error { + return ErrFalseRollback +} + +type playerBundle struct { + mutex sync.RWMutex + tx db.Transact + errTx error + errNew error + errFind error + errSave error + records map[string]model.Player +} + +func (pb *playerBundle) Transaction() (db.Transact, error) { + return pb.tx, pb.errTx +} + +func (pb *playerBundle) NewPlayer(ID string, tx db.Transact) error { + pb.mutex.RLock() + defer pb.mutex.RUnlock() + _, ok := pb.records[ID] + if ok { + return ErrAlreadyExist + } + pb.records[ID] = model.Player{ID: ID} + return pb.errNew +} + +func (pb *playerBundle) FindPlayer(ID string, tx db.Transact) (*model.Player, error) { + pb.mutex.RLock() + defer pb.mutex.RUnlock() + player, ok := pb.records[ID] + if !ok { + return nil, ErrNotExist + } + return &player, pb.errFind +} + +func (pb *playerBundle) SavePlayer(player *model.Player, tx db.Transact) error { + pb.mutex.Lock() + defer pb.mutex.Unlock() + + pb.records[player.ID] = *player + return pb.errSave +} + +func TestNewPlayer(t *testing.T) { + + store := &playerBundle{ + tx: new(playerTxSuccess), + records: make(map[string]model.Player), + } + entry, err := New("p1", store) + test(t, err == nil, "Expected creating a new player, got", err) + if entry == nil { + t.Fatal("Expected player entry, got nil") + } + test(t, entry.Player.ID == "p1", "Expected player id: p1, got", entry.Player.ID) + entryExists, err := New("p1", store) + test(t, err == nil, "Expected find existing player, got", err) + if entryExists == nil { + t.Fatal("Expected player entry, got nil") + } + test(t, entry.Player.ID == entryExists.Player.ID, + "Expected the players id's are equal, got", entryExists.Player.ID) + test(t, entry.Player.Balance == entryExists.Player.Balance, + "Expected the players balances are equal, got", entryExists.Player.Balance) + store.errTx = ErrFalseTransaction + _, err = New("p2", store) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.tx = new(playerTxFalse) + store.errTx = nil + _, err = New("p3", store) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.errNew = ErrNewPlayer + _, err = New("p4", store) + test(t, err == ErrNewPlayer, "Expected", ErrNewPlayer, "got", err) +} + +func TestPlayerFund(t *testing.T) { + + store := &playerBundle{ + tx: new(playerTxSuccess), + records: make(map[string]model.Player), + } + entry, err := New("p1", store) + test(t, err == nil, "Expected creating a new player, got", err) + err = entry.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + points, err := entry.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, points == 300, "Expected 300 points for the player, got", points) + store.errTx = ErrFalseTransaction + err = entry.Fund(10) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.tx = new(playerTxFalse) + store.errTx = nil + err = entry.Fund(20) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.tx = new(playerTxSuccess) + store.errFind = ErrFindPlayer + err = entry.Fund(30) + test(t, err == ErrFindPlayer, "Expected", ErrFindPlayer, "got", err) + store.errFind = nil + store.errSave = ErrSavePlayer + err = entry.Fund(40) + test(t, err == ErrSavePlayer, "Expected", ErrSavePlayer, "got", err) +} + +func TestPlayerTake(t *testing.T) { + + store := &playerBundle{ + tx: new(playerTxSuccess), + records: make(map[string]model.Player), + } + entry, err := New("p3", store) + test(t, err == nil, "Expected creating a new player, got", err) + err = entry.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = entry.Take(400) + test(t, err != nil, "Expected take more than player balance, got success") + err = entry.Take(200) + test(t, err == nil, "Expected take amount from player balance, got", err) + balance, err := entry.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 100, "Expected 100 points for the player, got", balance) + store.errTx = ErrFalseTransaction + err = entry.Take(10) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.tx = new(playerTxFalse) + store.errTx = nil + err = entry.Take(20) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.tx = new(playerTxSuccess) + store.errFind = ErrFindPlayer + err = entry.Take(30) + test(t, err == ErrFindPlayer, "Expected", ErrFindPlayer, "got", err) + store.errFind = nil + store.errSave = ErrSavePlayer + err = entry.Take(40) + test(t, err == ErrSavePlayer, "Expected", ErrSavePlayer, "got", err) +} + +func TestPlayerBalance(t *testing.T) { + + store := &playerBundle{ + tx: new(playerTxSuccess), + records: make(map[string]model.Player), + } + entry, err := New("p4", store) + test(t, err == nil, "Expected creating a new player, got", err) + balance, err := entry.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 0, "Expected 0 points for the player, got", balance) + err = entry.Fund(50.99) + test(t, err == nil, "Expected fund 50.99 to the player, got", err) + balance, err = entry.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 50.99, "Expected 300 points for the player, got", balance) + store.errFind = ErrFindPlayer + _, err = entry.Balance() + test(t, err == ErrFindPlayer, "Expected", ErrFindPlayer, "got", err) +} + +func TestPlayerID(t *testing.T) { + + store := &playerBundle{ + tx: new(playerTxSuccess), + records: make(map[string]model.Player), + } + entry, err := New("p1", store) + test(t, err == nil, "Expected creating a new player, got", err) + id := entry.ID() + test(t, id == entry.Player.ID, "Expected the player id,", entry.Player.ID, " got", id) +} diff --git a/tournament/controller.go b/tournament/controller.go new file mode 100644 index 0000000..670dd63 --- /dev/null +++ b/tournament/controller.go @@ -0,0 +1,14 @@ +package tournament + +import ( + "github.com/takama/backer/db" + "github.com/takama/backer/model" +) + +// Controller defines DB interface for Entry +type Controller interface { + Transaction() (db.Transact, error) + NewTournament(ID uint64, tx db.Transact) error + FindTournament(ID uint64, tx db.Transact) (*model.Tournament, error) + SaveTournament(tournament *model.Tournament, tx db.Transact) error +} diff --git a/tournament/tournament.go b/tournament/tournament.go new file mode 100644 index 0000000..19e0fce --- /dev/null +++ b/tournament/tournament.go @@ -0,0 +1,196 @@ +package tournament + +import ( + "errors" + "sync" + + "github.com/takama/backer" + "github.com/takama/backer/helper" + "github.com/takama/backer/model" +) + +var ( + // ErrAllreadyFinished appears if tournament was already finished + ErrAllreadyFinished = errors.New("Tournament already finished") +) + +// Entry implements Tournament interface +type Entry struct { + Controller + mutex sync.RWMutex + model.Tournament +} + +// New returns new Entry which implement Tournament interface +func New(id uint64, ctrl Controller) (*Entry, error) { + tx, err := ctrl.Transaction() + if err != nil { + return nil, err + } + + entry := &Entry{Controller: ctrl} + + tournament, err := ctrl.FindTournament(id, tx) + if err != nil { + err = ctrl.NewTournament(id, tx) + if err != nil { + tx.Rollback() + return nil, err + } + tournament = &model.Tournament{ID: id} + } + entry.Tournament = *tournament + + err = tx.Commit() + if err != nil { + return nil, err + } + + return entry, nil +} + +// Announce tournament with specified deposit +func (entry *Entry) Announce(deposit backer.Points) error { + tx, err := entry.Controller.Transaction() + if err != nil { + return err + } + + tournament, err := entry.Controller.FindTournament(entry.Tournament.ID, tx) + if err != nil { + tx.Rollback() + return err + } + + if tournament.IsFinished { + return ErrAllreadyFinished + } + + tournament.Deposit = backer.Points(helper.TruncatePrice(float32(deposit))) + err = entry.Controller.SaveTournament(tournament, tx) + if err != nil { + tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + entry.Tournament.Deposit = tournament.Deposit + + return nil +} + +// Join player and backers into a tournament +func (entry *Entry) Join(players ...backer.Player) error { + tx, err := entry.Controller.Transaction() + if err != nil { + return err + } + + tournament, err := entry.Controller.FindTournament(entry.Tournament.ID, tx) + if err != nil { + tx.Rollback() + return err + } + + if tournament.IsFinished { + return ErrAllreadyFinished + } + + var bidder model.Bidder + contribute := float32(tournament.Deposit / backer.Points(len(players))) + for idx, player := range players { + err := player.Take(backer.Points(contribute)) + if err != nil { + tx.Rollback() + return err + } + if idx == 0 { + bidder.ID = player.ID() + } else { + bidder.Backers = append(bidder.Backers, player) + } + } + tournament.Bidders = append(tournament.Bidders, bidder) + + err = entry.Controller.SaveTournament(tournament, tx) + if err != nil { + tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + entry.Tournament.Bidders = tournament.Bidders + + return nil +} + +// Result tournament prizes and winners +func (entry *Entry) Result(winners map[backer.Player]backer.Points) error { + tx, err := entry.Controller.Transaction() + if err != nil { + return err + } + + tournament, err := entry.Controller.FindTournament(entry.Tournament.ID, tx) + if err != nil { + tx.Rollback() + return err + } + + if tournament.IsFinished { + return ErrAllreadyFinished + } + + entry.mutex.Lock() + defer entry.mutex.Unlock() + + for player, points := range winners { + for idx, bidder := range tournament.Bidders { + if bidder.ID == player.ID() { + tournament.Bidders[idx].Winner = true + prize := float32(points / backer.Points(len(bidder.Backers)+1)) + err := player.Fund(backer.Points(prize)) + if err != nil { + tx.Rollback() + return err + } + for _, p := range bidder.Backers { + err := p.Fund(backer.Points(prize)) + if err != nil { + tx.Rollback() + return err + } + } + } + } + } + + tournament.IsFinished = true + + err = entry.Controller.SaveTournament(tournament, tx) + if err != nil { + tx.Rollback() + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + entry.Tournament.IsFinished = tournament.IsFinished + + return nil +} diff --git a/tournament/tournament_test.go b/tournament/tournament_test.go new file mode 100644 index 0000000..9b3c01e --- /dev/null +++ b/tournament/tournament_test.go @@ -0,0 +1,426 @@ +package tournament + +import ( + "errors" + "sync" + "testing" + + "github.com/takama/backer" + "github.com/takama/backer/db" + "github.com/takama/backer/model" + "github.com/takama/backer/player" +) + +var ( + ErrFalseTransaction = errors.New("Test false transaction") + ErrFalseCommit = errors.New("Test false commit") + ErrFalseRollback = errors.New("Test false rollback") + ErrNewTournament = errors.New("Test new tournament with error") + ErrFindTournament = errors.New("Test find tournament with error") + ErrSaveTournament = errors.New("Test save tournament with error") + ErrAlreadyExist = errors.New("Record already exists") + ErrNotExist = errors.New("Record does not exist") +) + +func test(t *testing.T, expected bool, messages ...interface{}) { + if !expected { + t.Error(messages) + } +} + +type tournamentTxSuccess struct{} + +func (ptx tournamentTxSuccess) Commit() error { + return nil +} + +func (ptx tournamentTxSuccess) Rollback() error { + return nil +} + +type tournamentTxFalse struct{} + +func (ptx tournamentTxFalse) Commit() error { + return ErrFalseCommit +} + +func (ptx tournamentTxFalse) Rollback() error { + return ErrFalseRollback +} + +type tournamentBundle struct { + mutex sync.RWMutex + tx db.Transact + errTx error + errNew error + errFind error + errSave error + records map[uint64]model.Tournament +} + +func (trn *tournamentBundle) Transaction() (db.Transact, error) { + return trn.tx, trn.errTx +} + +func (trn *tournamentBundle) NewTournament(ID uint64, tx db.Transact) error { + trn.mutex.RLock() + defer trn.mutex.RUnlock() + _, ok := trn.records[ID] + if ok { + return ErrAlreadyExist + } + trn.records[ID] = model.Tournament{ID: ID} + return trn.errNew +} + +func (trn *tournamentBundle) FindTournament(ID uint64, tx db.Transact) (*model.Tournament, error) { + trn.mutex.RLock() + defer trn.mutex.RUnlock() + tournament, ok := trn.records[ID] + if !ok { + return nil, ErrNotExist + } + return &tournament, trn.errFind +} + +func (trn *tournamentBundle) SaveTournament(tournament *model.Tournament, tx db.Transact) error { + trn.mutex.Lock() + defer trn.mutex.Unlock() + + trn.records[tournament.ID] = *tournament + return trn.errSave +} + +type playerTx struct{} + +func (ptx playerTx) Commit() error { + return nil +} + +func (ptx playerTx) Rollback() error { + return nil +} + +type playerBundle struct { + mutex sync.RWMutex + tx db.Transact + errTx error + errNew error + errFind error + errSave error + records map[string]model.Player +} + +func (pb *playerBundle) Transaction() (db.Transact, error) { + pb.tx = new(playerTx) + return pb.tx, pb.errTx +} + +func (pb *playerBundle) NewPlayer(ID string, tx db.Transact) error { + pb.mutex.RLock() + defer pb.mutex.RUnlock() + _, ok := pb.records[ID] + if ok { + return ErrAlreadyExist + } + pb.records[ID] = model.Player{ID: ID} + return pb.errNew +} + +func (pb *playerBundle) FindPlayer(ID string, tx db.Transact) (*model.Player, error) { + pb.mutex.RLock() + defer pb.mutex.RUnlock() + player, ok := pb.records[ID] + if !ok { + return nil, ErrNotExist + } + return &player, pb.errFind +} + +func (pb *playerBundle) SavePlayer(player *model.Player, tx db.Transact) error { + pb.mutex.Lock() + defer pb.mutex.Unlock() + + pb.records[player.ID] = *player + return pb.errSave +} + +func TestNewTournament(t *testing.T) { + + store := &tournamentBundle{ + tx: new(tournamentTxSuccess), + records: make(map[uint64]model.Tournament), + } + tournament, err := New(1, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + if tournament == nil { + t.Fatal("Expected tournament entry, got nil") + } + test(t, tournament.ID == 1, "Expected tournament id: p1, got", tournament.ID) + tournamentExists, err := New(1, store) + test(t, err == nil, "Expected find existing tournament, got", err) + if tournamentExists == nil { + t.Fatal("Expected tournament entry, got nil") + } + test(t, tournament.ID == tournamentExists.ID, "Expected the tournaments id's are equal, got", tournamentExists.ID) + store.errTx = ErrFalseTransaction + _, err = New(2, store) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.tx = new(tournamentTxFalse) + store.errTx = nil + _, err = New(3, store) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.errNew = ErrNewTournament + _, err = New(4, store) + test(t, err == ErrNewTournament, "Expected", ErrNewTournament, "got", err) +} + +func TestTournamentAnnounce(t *testing.T) { + + store := &tournamentBundle{ + tx: new(tournamentTxSuccess), + records: make(map[uint64]model.Tournament), + } + tournament, err := New(1, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + store.errTx = ErrFalseTransaction + err = tournament.Announce(2000) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.tx = new(tournamentTxFalse) + store.errTx = nil + err = tournament.Announce(3000) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.tx = new(tournamentTxSuccess) + store.errFind = ErrFindTournament + err = tournament.Announce(300) + test(t, err == ErrFindTournament, "Expected", ErrFindTournament, "got", err) + store.errFind = nil + store.errSave = ErrSaveTournament + err = tournament.Announce(500) + test(t, err == ErrSaveTournament, "Expected", ErrSaveTournament, "got", err) + store.errSave = nil + err = tournament.Result(nil) + err = tournament.Announce(700) + test(t, err == ErrAllreadyFinished, "Expected", ErrAllreadyFinished, "got", err) +} + +func TestTournamentJoin(t *testing.T) { + + playerStore := &playerBundle{ + records: make(map[string]model.Player), + } + playerP1, err := player.New("p1", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + + store := &tournamentBundle{ + tx: new(tournamentTxSuccess), + records: make(map[uint64]model.Tournament), + } + tournament, err := New(1, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + + err = tournament.Join(playerP1) + test(t, err == player.ErrInsufficientPoints, "Expected", player.ErrInsufficientPoints, "got", err) + err = playerP1.Fund(1000) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + + store.errTx = ErrFalseTransaction + err = tournament.Join(playerP1) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.errTx = nil + store.errFind = ErrFindTournament + err = tournament.Join(playerP1) + test(t, err == ErrFindTournament, "Expected", ErrFindTournament, "got", err) + store.errFind = nil + store.errSave = ErrSaveTournament + err = tournament.Join(playerP1) + test(t, err == ErrSaveTournament, "Expected", ErrSaveTournament, "got", err) + err = playerP1.Fund(1000) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + store.errSave = nil + store.tx = new(tournamentTxFalse) + err = tournament.Join(playerP1) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + err = playerP1.Fund(1000) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + store.tx = new(tournamentTxSuccess) + + err = tournament.Join(playerP1) + test(t, err == nil, "Expected join a player, got", err) + + playerP2, err := player.New("p2", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB1, err := player.New("b1", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB2, err := player.New("b2", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB3, err := player.New("b3", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + err = tournament.Join(playerP2, playerB1, playerB2, playerB3) + test(t, err == player.ErrInsufficientPoints, "Expected", player.ErrInsufficientPoints, "got", err) + err = playerP2.Fund(500) + test(t, err == nil, "Expected fund 500 to the player, got", err) + err = playerB1.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = playerB2.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = playerB3.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = tournament.Join(playerP2, playerB1, playerB2, playerB3) + test(t, err == nil, "Expected join a player, got", err) + + err = tournament.Result(nil) + playerP3, err := player.New("p3", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + err = tournament.Join(playerP3) + test(t, err == ErrAllreadyFinished, "Expected", ErrAllreadyFinished, "got", err) +} + +func TestTournamentResult(t *testing.T) { + + playerStore := &playerBundle{ + records: make(map[string]model.Player), + } + playerP1, err := player.New("p1", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + + store := &tournamentBundle{ + tx: new(tournamentTxSuccess), + records: make(map[uint64]model.Tournament), + } + tournament, err := New(1, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + + err = playerP1.Fund(1000) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + err = tournament.Join(playerP1) + test(t, err == nil, "Expected join a player, got", err) + + playerP2, err := player.New("p2", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB1, err := player.New("b1", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB2, err := player.New("b2", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + playerB3, err := player.New("b3", playerStore) + test(t, err == nil, "Expected creating a new player, got", err) + err = playerP2.Fund(500) + test(t, err == nil, "Expected fund 500 to the player, got", err) + err = playerB1.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = playerB2.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = playerB3.Fund(300) + test(t, err == nil, "Expected fund 300 to the player, got", err) + err = tournament.Join(playerP2, playerB1, playerB2, playerB3) + test(t, err == nil, "Expected join a player, got", err) + + winners := make(map[backer.Player]backer.Points) + winners[playerP2] = 2000 + + store.errTx = ErrFalseTransaction + err = tournament.Result(winners) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + store.errTx = nil + store.errFind = ErrFindTournament + err = tournament.Result(winners) + test(t, err == ErrFindTournament, "Expected", ErrFindTournament, "got", err) + store.errFind = nil + + err = tournament.Result(winners) + test(t, err == nil, "Expected result of the tournament, got", err) + + balance, err := playerP1.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 0, "Expected 0 points for the player, got", balance) + balance, err = playerP2.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 750, "Expected 750 points for the player, got", balance) + balance, err = playerB1.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 550, "Expected 550 points for the player, got", balance) + balance, err = playerB2.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 550, "Expected 550 points for the player, got", balance) + balance, err = playerB3.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 550, "Expected 550 points for the player, got", balance) + + err = tournament.Result(winners) + test(t, err == ErrAllreadyFinished, "Expected", ErrAllreadyFinished, "got", err) + + tournament, err = New(2, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + store.errSave = ErrSaveTournament + err = tournament.Result(winners) + test(t, err == ErrSaveTournament, "Expected", ErrSaveTournament, "got", err) + store.errSave = nil + + tournament, err = New(3, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + store.tx = new(tournamentTxFalse) + err = tournament.Result(winners) + test(t, err == ErrFalseCommit, "Expected", ErrFalseCommit, "got", err) + store.tx = new(tournamentTxSuccess) + + tournament, err = New(4, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + err = playerP2.Fund(250) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + err = tournament.Join(playerP2) + test(t, err == nil, "Expected join a player, got", err) + playerStore.errTx = ErrFalseTransaction + err = tournament.Result(winners) + test(t, err == ErrFalseTransaction, "Expected", ErrFalseTransaction, "got", err) + playerStore.errTx = nil + + tournament, err = New(5, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + err = playerP1.Fund(250) + test(t, err == nil, "Expected fund 250 to the player, got", err) + err = tournament.Join(playerP1, playerB1, playerB2, playerB3) + test(t, err == nil, "Expected join players, got", err) + winners = make(map[backer.Player]backer.Points) + delete(playerStore.records, "b3") + winners[playerP1] = 1000 + err = tournament.Result(winners) + test(t, err == ErrNotExist, "Expected", ErrNotExist, "got", err) + + tournament, err = New(6, store) + test(t, err == nil, "Expected creating a new tournament, got", err) + err = tournament.Announce(1000) + test(t, err == nil, "Expected announce of the tournament, got", err) + err = playerP1.Fund(400) + test(t, err == nil, "Expected fund 250 to the player, got", err) + err = tournament.Join(playerP1, playerB1, playerB2) + test(t, err == nil, "Expected join players, got", err) + err = playerP2.Fund(1000) + test(t, err == nil, "Expected fund 1000 to the player, got", err) + err = tournament.Join(playerP2) + test(t, err == nil, "Expected join players, got", err) + + winners = make(map[backer.Player]backer.Points) + winners[playerP1] = 2000 + err = tournament.Result(winners) + test(t, err == nil, "Expected result of the tournament, got", err) + + balance, err = playerP1.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 983.33, "Expected 983.33 points for the player, got", balance) + balance, err = playerB1.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 883.33, "Expected 883.33 points for the player, got", balance) + balance, err = playerB2.Balance() + test(t, err == nil, "Expected check balance of the player, got", err) + test(t, balance == 883.33, "Expected 883.33 points for the player, got", balance) +}