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\Multiplayer;
9
10use App\Exceptions\InvariantException;
11use App\Models\Beatmap;
12use App\Models\Multiplayer\PlaylistItem;
13use App\Models\Multiplayer\ScoreLink;
14use App\Models\ScoreToken;
15use Tests\TestCase;
16
17class ScoreLinkTest extends TestCase
18{
19 private static array $commonScoreParams;
20
21 public static function setUpBeforeClass(): void
22 {
23 parent::setUpBeforeClass();
24 static::$commonScoreParams = [
25 'accuracy' => 0.5,
26 'ended_at' => new \DateTime(),
27 'max_combo' => 1,
28 'statistics' => [
29 'great' => 1,
30 ],
31 'total_score' => 1,
32 ];
33 }
34
35 public function testRequiredModsMissing()
36 {
37 $playlistItem = PlaylistItem::factory()->create([
38 'required_mods' => [[
39 'acronym' => 'HD',
40 ]],
41 ]);
42 $scoreToken = ScoreToken::factory()->create([
43 'beatmap_id' => $playlistItem->beatmap_id,
44 'playlist_item_id' => $playlistItem,
45 ]);
46
47 $this->expectException(InvariantException::class);
48 $this->expectExceptionMessage('This play does not include the mods required.');
49 ScoreLink::complete($scoreToken, [
50 ...static::$commonScoreParams,
51 'beatmap_id' => $playlistItem->beatmap_id,
52 'ruleset_id' => $playlistItem->ruleset_id,
53 'user_id' => $scoreToken->user_id,
54 ]);
55 }
56
57 public function testRequiredModsPresent()
58 {
59 $playlistItem = PlaylistItem::factory()->create([
60 'required_mods' => [[
61 'acronym' => 'HD',
62 ]],
63 ]);
64 $scoreToken = ScoreToken::factory()->create([
65 'beatmap_id' => $playlistItem->beatmap_id,
66 'playlist_item_id' => $playlistItem,
67 ]);
68
69 $this->expectNotToPerformAssertions();
70 ScoreLink::complete($scoreToken, [
71 ...static::$commonScoreParams,
72 'beatmap_id' => $playlistItem->beatmap_id,
73 'mods' => [['acronym' => 'HD']],
74 'ruleset_id' => $playlistItem->ruleset_id,
75 'user_id' => $scoreToken->user_id,
76 ]);
77 }
78
79 public function testExpectedAllowedMod()
80 {
81 $playlistItem = PlaylistItem::factory()->create([
82 'required_mods' => [[
83 'acronym' => 'DT',
84 ]],
85 'allowed_mods' => [[
86 'acronym' => 'HD',
87 ]],
88 ]);
89 $scoreToken = ScoreToken::factory()->create([
90 'beatmap_id' => $playlistItem->beatmap_id,
91 'playlist_item_id' => $playlistItem,
92 ]);
93
94 $this->expectNotToPerformAssertions();
95 ScoreLink::complete($scoreToken, [
96 ...static::$commonScoreParams,
97 'beatmap_id' => $playlistItem->beatmap_id,
98 'mods' => [
99 ['acronym' => 'DT'],
100 ['acronym' => 'HD'],
101 ],
102 'ruleset_id' => $playlistItem->ruleset_id,
103 'user_id' => $scoreToken->user_id,
104 ]);
105 }
106
107 public function testUnexpectedAllowedMod()
108 {
109 $playlistItem = PlaylistItem::factory()->create([
110 'required_mods' => [[
111 'acronym' => 'DT',
112 ]],
113 'allowed_mods' => [[
114 'acronym' => 'HR',
115 ]],
116 ]);
117 $scoreToken = ScoreToken::factory()->create([
118 'beatmap_id' => $playlistItem->beatmap_id,
119 'playlist_item_id' => $playlistItem,
120 ]);
121
122 $this->expectException(InvariantException::class);
123 $this->expectExceptionMessage('This play includes mods that are not allowed.');
124 ScoreLink::complete($scoreToken, [
125 ...static::$commonScoreParams,
126 'beatmap_id' => $playlistItem->beatmap_id,
127 'mods' => [
128 ['acronym' => 'DT'],
129 ['acronym' => 'HD'],
130 ],
131 'ruleset_id' => $playlistItem->ruleset_id,
132 'user_id' => $scoreToken->user_id,
133 ]);
134 }
135
136 public function testUnexpectedModWhenNoModsAreAllowed()
137 {
138 $playlistItem = PlaylistItem::factory()->create(); // no required or allowed mods.
139 $scoreToken = ScoreToken::factory()->create([
140 'beatmap_id' => $playlistItem->beatmap_id,
141 'playlist_item_id' => $playlistItem,
142 ]);
143
144 $this->expectException(InvariantException::class);
145 $this->expectExceptionMessage('This play includes mods that are not allowed.');
146 ScoreLink::complete($scoreToken, [
147 ...static::$commonScoreParams,
148 'beatmap_id' => $playlistItem->beatmap_id,
149 'mods' => [['acronym' => 'HD']],
150 'ruleset_id' => $playlistItem->ruleset_id,
151 'user_id' => $scoreToken->user_id,
152 ]);
153 }
154
155 public function testUnexpectedModAcceptedIfAlwaysValidForSubmission()
156 {
157 $beatmap = Beatmap::factory()->create([
158 'playmode' => 0, // must be osu! specifically. no other ruleset currently has an appropriate mod.
159 ]);
160 $playlistItem = PlaylistItem::factory()->create([
161 'ruleset_id' => 0,
162 'beatmap_id' => $beatmap,
163 // no required or allowed mods.
164 ]);
165 $scoreToken = ScoreToken::factory()->create([
166 'beatmap_id' => $playlistItem->beatmap_id,
167 'playlist_item_id' => $playlistItem,
168 ]);
169
170 $this->expectNotToPerformAssertions();
171 ScoreLink::complete($scoreToken, [
172 ...static::$commonScoreParams,
173 'beatmap_id' => $playlistItem->beatmap_id,
174 'mods' => [['acronym' => 'TD']],
175 'ruleset_id' => $playlistItem->ruleset_id,
176 'user_id' => $scoreToken->user_id,
177 ]);
178 }
179
180 public function testFailedMultiplayerScoresArePreserved()
181 {
182 $playlistItem = PlaylistItem::factory()->create();
183 $scoreToken = ScoreToken::factory()->create([
184 'beatmap_id' => $playlistItem->beatmap_id,
185 'playlist_item_id' => $playlistItem,
186 ]);
187
188 $scoreLink = ScoreLink::complete($scoreToken, [
189 ...static::$commonScoreParams,
190 'beatmap_id' => $playlistItem->beatmap_id,
191 'ruleset_id' => $playlistItem->ruleset_id,
192 'user_id' => $scoreToken->user_id,
193 'passed' => false,
194 ]);
195 $this->assertTrue($scoreLink->score->preserve);
196 }
197}