| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- <?php
- namespace Tests\Unit;
- use App\Services\WorldCup\Repositories\WorldCupSettlementRepositoryInterface;
- use App\Services\WorldCup\WorldCupSettlementService;
- use Tests\TestCase;
- class WorldCupSettlementServiceTest extends TestCase
- {
- public function test_settle_match_marks_won_and_lost_bets_and_sends_reward_mail()
- {
- $repository = new InMemoryWorldCupSettlementRepository();
- $service = new WorldCupSettlementService($repository);
- $result = $service->settleMatch(10, 'home', 'admin-01');
- $this->assertTrue($result['success']);
- $this->assertSame(1, $result['data']['won_count']);
- $this->assertSame(1, $result['data']['lost_count']);
- $this->assertSame('finished', $repository->matches[10]['status']);
- $this->assertSame('home', $repository->matches[10]['result']);
- $this->assertSame('won', $repository->bets[1]['status']);
- $this->assertSame('lost', $repository->bets[2]['status']);
- $this->assertSame(2650, $repository->mails[1001][0]['amount']);
- $this->assertSame('You won $26.50 · Brazil vs Serbia', $repository->mails[1001][0]['title']);
- $this->assertStringContainsString('Your bet: Brazil win · $10.00 · Odds 2.15', $repository->mails[1001][0]['text']);
- $this->assertSame(2650, $result['data']['paid_amount']);
- $this->assertSame('settle_match', $repository->audits[0]['action']);
- }
- public function test_settle_match_is_idempotent_after_finished()
- {
- $repository = new InMemoryWorldCupSettlementRepository();
- $service = new WorldCupSettlementService($repository);
- $first = $service->settleMatch(10, 'home', 'admin-01');
- $second = $service->settleMatch(10, 'home', 'admin-01');
- $this->assertSame(1, $first['data']['won_count']);
- $this->assertFalse($second['success']);
- $this->assertSame('Match already settled', $second['message']);
- $this->assertCount(1, $repository->mails[1001]);
- $this->assertCount(1, $repository->audits);
- }
- public function test_settle_match_does_not_override_finished_result()
- {
- $repository = new InMemoryWorldCupSettlementRepository();
- $service = new WorldCupSettlementService($repository);
- $service->settleMatch(10, 'home', 'admin-01');
- $result = $service->settleMatch(10, 'away', 'admin-01');
- $this->assertFalse($result['success']);
- $this->assertSame('Match already settled', $result['message']);
- $this->assertSame('home', $repository->matches[10]['result']);
- $this->assertSame('won', $repository->bets[1]['status']);
- $this->assertSame('lost', $repository->bets[2]['status']);
- }
- public function test_settle_match_rejects_invalid_result()
- {
- $service = new WorldCupSettlementService(new InMemoryWorldCupSettlementRepository());
- $result = $service->settleMatch(10, 'Brazil', 'admin-01');
- $this->assertFalse($result['success']);
- $this->assertSame('Invalid match result', $result['message']);
- }
- public function test_settle_match_rejects_draw_outside_group_stage()
- {
- $repository = new InMemoryWorldCupSettlementRepository();
- $service = new WorldCupSettlementService($repository);
- $result = $service->settleMatch(11, 'draw', 'admin-01');
- $this->assertFalse($result['success']);
- $this->assertSame('Draw is only available for group stage', $result['message']);
- $this->assertSame('scheduled', $repository->matches[11]['status']);
- $this->assertNull($repository->matches[11]['result']);
- }
- public function test_settle_winner_market_pays_selected_team()
- {
- $repository = new InMemoryWorldCupSettlementRepository();
- $service = new WorldCupSettlementService($repository);
- $result = $service->settleWinner('Brazil', 'admin-01');
- $this->assertTrue($result['success']);
- $this->assertSame('won', $repository->bets[3]['status']);
- $this->assertSame('lost', $repository->bets[4]['status']);
- $this->assertSame(6500, $repository->mails[1003][0]['amount']);
- $this->assertSame('You won $65.00 · World Cup 2026 Winner', $repository->mails[1003][0]['title']);
- }
- }
- class InMemoryWorldCupSettlementRepository implements WorldCupSettlementRepositoryInterface
- {
- public $matches = [
- 10 => [
- 'match_id' => 10,
- 'stage' => 'group',
- 'home_team' => 'Brazil',
- 'away_team' => 'Serbia',
- 'status' => 'scheduled',
- 'result' => null,
- ],
- 11 => [
- 'match_id' => 11,
- 'stage' => 'round_16',
- 'home_team' => 'Argentina',
- 'away_team' => 'France',
- 'status' => 'scheduled',
- 'result' => null,
- ],
- ];
- public $bets = [
- 1 => [
- 'bet_id' => 1,
- 'user_id' => 1001,
- 'market' => '1x2',
- 'match_id' => 10,
- 'selection' => 'home',
- 'stake' => 1000,
- 'odds' => 2.15,
- 'status' => 'pending',
- 'potential_payout' => 2650,
- ],
- 2 => [
- 'bet_id' => 2,
- 'user_id' => 1002,
- 'market' => '1x2',
- 'match_id' => 10,
- 'selection' => 'away',
- 'stake' => 1000,
- 'odds' => 1.95,
- 'status' => 'pending',
- 'potential_payout' => 1950,
- ],
- 3 => [
- 'bet_id' => 3,
- 'user_id' => 1003,
- 'market' => 'winner',
- 'match_id' => null,
- 'selection' => 'Brazil',
- 'stake' => 1000,
- 'odds' => 6.5,
- 'status' => 'pending',
- 'potential_payout' => 6500,
- ],
- 4 => [
- 'bet_id' => 4,
- 'user_id' => 1004,
- 'market' => 'winner',
- 'match_id' => null,
- 'selection' => 'Argentina',
- 'stake' => 1000,
- 'odds' => 6.0,
- 'status' => 'pending',
- 'potential_payout' => 6000,
- ],
- ];
- public $mails = [];
- public $audits = [];
- public function findMatch(int $matchId): ?array
- {
- return $this->matches[$matchId] ?? null;
- }
- public function markMatchFinished(int $matchId, string $result): void
- {
- $this->matches[$matchId]['status'] = 'finished';
- $this->matches[$matchId]['result'] = $result;
- }
- public function pendingBetsForMatch(int $matchId): array
- {
- return array_values(array_map(function (array $bet) use ($matchId) {
- return array_merge($this->matches[$matchId], $bet);
- }, array_filter($this->bets, function (array $bet) use ($matchId) {
- return $bet['market'] === '1x2'
- && (int)$bet['match_id'] === $matchId
- && $bet['status'] === 'pending';
- })));
- }
- public function pendingWinnerBets(): array
- {
- return array_values(array_filter($this->bets, function (array $bet) {
- return $bet['market'] === 'winner' && $bet['status'] === 'pending';
- }));
- }
- public function markBetSettled(int $betId, string $status): void
- {
- $this->bets[$betId]['status'] = $status;
- }
- public function payBet(array $bet): int
- {
- $odds = (float)$bet['odds'];
- $payout = (int)$bet['potential_payout'];
- $title = $bet['market'] === 'winner'
- ? 'You won $65.00 · World Cup 2026 Winner'
- : 'You won $26.50 · Brazil vs Serbia';
- $text = $bet['market'] === 'winner'
- ? 'Your bet: Brazil to win · $10.00 · Odds ' . number_format($odds, 2)
- : 'Your bet: Brazil win · $10.00 · Odds ' . number_format($odds, 2);
- $this->mails[$bet['user_id']][] = [
- 'title' => $title,
- 'text' => $text,
- 'amount' => $payout,
- ];
- return $payout;
- }
- public function writeAudit(string $actor, string $action, array $payload): void
- {
- $this->audits[] = compact('actor', 'action', 'payload');
- }
- }
|