toggleFavorite(1001, 10, $now); $removed = $service->toggleFavorite(1001, 10, $now); $this->assertTrue($added['success']); $this->assertTrue($added['is_favorite']); $this->assertTrue($removed['success']); $this->assertFalse($removed['is_favorite']); } public function test_match_list_marks_only_current_user_favorites() { $repository = new InMemoryWorldCupMatchRepository(); $repository->addFavorite(1001, 10); $repository->addFavorite(2002, 11); $service = new WorldCupMatchFavoriteService($repository); $matches = $service->listMatches(1001, false, Carbon::parse('2026-06-05 12:00:00')); $this->assertTrue($matches[0]['is_favorite']); $this->assertFalse($matches[1]['is_favorite']); } public function test_match_list_keeps_schedule_metadata_for_frontend() { $repository = new InMemoryWorldCupMatchRepository(); $service = new WorldCupMatchFavoriteService($repository); $matches = $service->listMatches(1001, false, Carbon::parse('2026-06-05 12:00:00')); $this->assertSame(1, $matches[0]['match_no']); $this->assertSame('group', $matches[0]['stage']); $this->assertSame('A', $matches[0]['group_name']); $this->assertSame('Mexico City Stadium', $matches[0]['venue']); } public function test_match_list_returns_closed_future_schedule_as_not_bettable() { $repository = new InMemoryWorldCupMatchRepository(); $service = new WorldCupMatchFavoriteService($repository); $matches = $service->listMatches(1001, false, Carbon::parse('2026-06-05 12:00:00')); $this->assertSame(73, $matches[2]['match_no']); $this->assertSame('Winner 73 A', $matches[2]['home_team']); $this->assertFalse($matches[2]['is_bettable']); } public function test_favorite_filter_returns_only_favorites_and_keeps_cutoff_filter() { $repository = new InMemoryWorldCupMatchRepository(); $repository->addFavorite(1001, 10); $repository->addFavorite(1001, 12); $service = new WorldCupMatchFavoriteService($repository); $matches = $service->listMatches(1001, true, Carbon::parse('2026-06-05 12:00:00')); $this->assertCount(1, $matches); $this->assertSame(10, $matches[0]['match_id']); $this->assertTrue($matches[0]['is_favorite']); } } class InMemoryWorldCupMatchRepository implements WorldCupMatchRepositoryInterface { private $favorites = []; private $matches = [ [ 'match_id' => 10, 'match_no' => 1, 'competition' => 'World Cup', 'stage' => 'group', 'group_name' => 'A', 'home_team' => 'Mexico', 'away_team' => 'South Africa', 'venue' => 'Mexico City Stadium', 'kickoff_at' => '2026-06-05 15:00:00', 'status' => 'scheduled', ], [ 'match_id' => 11, 'match_no' => 2, 'competition' => 'World Cup', 'stage' => 'group', 'group_name' => 'A', 'home_team' => 'France', 'away_team' => 'Spain', 'venue' => 'Estadio Guadalajara', 'kickoff_at' => '2026-06-05 18:00:00', 'status' => 'scheduled', ], [ 'match_id' => 12, 'match_no' => 3, 'competition' => 'World Cup', 'stage' => 'group', 'group_name' => 'B', 'home_team' => 'Germany', 'away_team' => 'Italy', 'venue' => 'Toronto Stadium', 'kickoff_at' => '2026-06-05 12:30:00', 'status' => 'scheduled', ], [ 'match_id' => 73, 'match_no' => 73, 'competition' => 'World Cup', 'stage' => 'round_32', 'group_name' => null, 'home_team' => 'Winner 73 A', 'away_team' => 'Winner 73 B', 'venue' => 'Los Angeles Stadium', 'kickoff_at' => '2026-07-01 12:00:00', 'status' => 'closed', ], ]; public function getScheduleMatches(Carbon $now): array { $cutoff = $now->copy()->addHour(); return array_values(array_filter($this->matches, function (array $match) use ($cutoff) { return in_array($match['status'], ['scheduled', 'closed'], true) && Carbon::parse($match['kickoff_at'])->gt($cutoff); })); } public function getOpenMatches(Carbon $now): array { $cutoff = $now->copy()->addHour(); return array_values(array_filter($this->matches, function (array $match) use ($cutoff) { return $match['status'] === 'scheduled' && Carbon::parse($match['kickoff_at'])->gt($cutoff); })); } public function isOpenMatch(int $matchId, Carbon $now): bool { foreach ($this->getOpenMatches($now) as $match) { if ((int)$match['match_id'] === $matchId) { return true; } } return false; } public function getFavoriteMatchIds(int $userId): array { return array_values($this->favorites[$userId] ?? []); } public function isFavorite(int $userId, int $matchId): bool { return in_array($matchId, $this->favorites[$userId] ?? [], true); } public function addFavorite(int $userId, int $matchId): void { if (!isset($this->favorites[$userId])) { $this->favorites[$userId] = []; } if (!$this->isFavorite($userId, $matchId)) { $this->favorites[$userId][] = $matchId; } } public function removeFavorite(int $userId, int $matchId): void { $this->favorites[$userId] = array_values(array_filter( $this->favorites[$userId] ?? [], function ($favoriteMatchId) use ($matchId) { return (int)$favoriteMatchId !== $matchId; } )); } }