1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6declare(strict_types=1);
7
8namespace Tests\Models;
9
10use App\Exceptions\InvariantException;
11use App\Models\Beatmap;
12use App\Models\Beatmapset;
13use App\Models\Contest;
14use App\Models\ContestEntry;
15use App\Models\Multiplayer\PlaylistItem;
16use App\Models\Multiplayer\Room;
17use App\Models\Multiplayer\ScoreLink as MultiplayerScoreLink;
18use App\Models\Multiplayer\UserScoreAggregate;
19use App\Models\User;
20use Carbon\Carbon;
21use Tests\TestCase;
22
23class ContestTest extends TestCase
24{
25 /**
26 * @dataProvider dataProviderForTestAssertVoteRequirementPlaylistBeatmapsets
27 */
28 public function testAssertVoteRequirementPlaylistBeatmapsets(
29 bool $loggedIn,
30 bool $played,
31 bool $passed,
32 ?bool $mustPass,
33 bool $canVote
34 ): void {
35 $beatmapsets = Beatmapset::factory()->count(5)->create();
36 $beatmaps = [];
37 foreach ($beatmapsets as $beatmapset) {
38 $beatmapsetId = $beatmapset->getKey();
39 for ($i = 0; $i < 2; $i++) {
40 $beatmaps[] = Beatmap::factory()->create(['beatmapset_id' => $beatmapsetId]);
41 }
42 }
43 // extra beatmap
44 Beatmap::factory()->create();
45
46 $rooms = Room::factory()->count(2)->create();
47 foreach ($rooms as $i => $room) {
48 foreach ($beatmapsets as $beatmapset) {
49 $playlistItems[] = PlaylistItem::factory()->create([
50 'room_id' => $room,
51 'beatmap_id' => $beatmapset->beatmaps[$i],
52 ]);
53 }
54 }
55 $contest = Contest::factory()->create([
56 'extra_options' => [
57 'requirement' => [
58 'must_pass' => $mustPass,
59 'name' => 'playlist_beatmapsets',
60 'room_ids' => array_column($rooms->all(), 'id'),
61 ],
62 ],
63 ]);
64 $entries = ContestEntry::factory()->count(2)->create(['contest_id' => $contest->getKey()]);
65
66 $user = $loggedIn ? User::factory()->create() : null;
67
68 if ($loggedIn && $played) {
69 $userId = $user->getKey();
70 $endedAt = json_time(Carbon::now());
71 foreach ($beatmapsets as $beatmapset) {
72 $room = array_rand_val($rooms);
73 $playlistItem = $room
74 ->playlist()
75 ->whereIn('beatmap_id', array_column($beatmapset->beatmaps->all(), 'beatmap_id'))
76 ->first();
77
78 MultiplayerScoreLink::factory()->state([
79 'playlist_item_id' => $playlistItem,
80 'user_id' => $userId,
81 ])->completed([
82 'ended_at' => $endedAt,
83 'passed' => $passed,
84 ])->create();
85 }
86 foreach ($rooms as $room) {
87 UserScoreAggregate::lookupOrDefault($user, $room)->recalculate();
88 }
89 }
90
91 if (!$canVote) {
92 $this->expectException(InvariantException::class);
93 }
94
95 $contest->assertVoteRequirement($user);
96
97 if ($canVote) {
98 $this->assertTrue(true, 'no exception');
99 }
100 }
101
102 public function testAssertVoteRequirementNoRequirement(): void
103 {
104 $contest = Contest::factory()->create();
105 $entry = ContestEntry::factory()->create(['contest_id' => $contest->getKey()]);
106 $user = User::factory()->create();
107
108 $contest->assertVoteRequirement($user, $entry);
109 $this->assertTrue(true, 'no exception');
110 }
111
112 /**
113 * @dataProvider dataProviderForTestShowEntryUser
114 */
115 public function testShowEntryUser(bool $showVotes, ?bool $showEntryUserOption, bool $result): void
116 {
117 $extraOptions = $showEntryUserOption === null
118 ? null
119 : ['show_entry_user' => $showEntryUserOption];
120 $contest = Contest::factory()->create([
121 'show_votes' => $showVotes,
122 'extra_options' => $extraOptions,
123 ]);
124 $this->assertSame($result, $contest->showEntryUser());
125 }
126
127 public static function dataProviderForTestAssertVoteRequirementPlaylistBeatmapsets(): array
128 {
129 return [
130 // when passing is required
131 [true, true, true, true, true],
132 [true, true, false, true, false],
133 [true, false, false, true, false],
134 [false, false, false, true, false],
135 // when passing is not specified (default required)
136 [true, true, true, null, true],
137 [true, true, false, null, false],
138 [true, false, false, null, false],
139 [false, false, false, null, false],
140 // when passing is not required
141 [true, true, true, false, true],
142 [true, true, false, false, true],
143 [true, false, false, false, false],
144 [false, false, false, false, false],
145 ];
146 }
147
148 public static function dataProviderForTestShowEntryUser(): array
149 {
150 return [
151 [false, null, false],
152 [true, null, true],
153 [false, false, false],
154 [true, false, true],
155 [false, true, true],
156 [true, true, true],
157 ];
158 }
159}