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
6namespace Tests\Controllers;
7
8use App\Models\Beatmap;
9use App\Models\Beatmapset;
10use App\Models\BeatmapsetEvent;
11use App\Models\Genre;
12use App\Models\Language;
13use App\Models\User;
14use Tests\TestCase;
15
16class BeatmapsetsControllerTest extends TestCase
17{
18 public function testBeatmapsetIsActive()
19 {
20 $beatmap = Beatmap::factory()->create();
21
22 $this->get(route('beatmapsets.show', ['beatmapset' => $beatmap->beatmapset_id]))
23 ->assertStatus(200);
24 }
25
26 public function testBeatmapsetIsNotActive()
27 {
28 $beatmap = Beatmap::factory()->create([
29 'beatmapset_id' => Beatmapset::factory()->inactive(),
30 ]);
31
32 $this->get(route('beatmapsets.show', ['beatmapset' => $beatmap->beatmapset_id]))
33 ->assertStatus(404);
34 }
35
36 public function testBeatmapsetWithDeletedBeatmap()
37 {
38 $beatmap = Beatmap::factory()->create([
39 'beatmapset_id' => Beatmapset::factory(),
40 'deleted_at' => now(),
41 ]);
42
43 $this->get(route('beatmapsets.show', ['beatmapset' => $beatmap->beatmapset_id]))
44 ->assertStatus(404);
45 }
46
47 public function testBeatmapsetWithNoBeatmaps()
48 {
49 $beatmapset = Beatmapset::factory()->create();
50
51 $this->get(route('beatmapsets.show', ['beatmapset' => $beatmapset->getKey()]))
52 ->assertStatus(404);
53 }
54
55 public function testBeatmapsetNominate()
56 {
57 $beatmapset = Beatmapset::factory()->create([
58 'approved' => Beatmapset::STATES['pending'],
59 ]);
60 $beatmap = Beatmap::factory()->create(['beatmapset_id' => $beatmapset->getKey()]);
61 $nominator = User::factory()->withGroup('bng', [$beatmap->mode])->create();
62
63 $this->actingAsVerified($nominator)
64 ->put(route('beatmapsets.nominate', ['beatmapset' => $beatmapset->getKey(), 'playmodes' => [$beatmap->mode]]))
65 ->assertSuccessful();
66
67 $this->assertSame(1, $beatmapset->beatmapsetNominations()->current()->count());
68 }
69
70 public function testBeatmapsetNominateOwnBeatmapset()
71 {
72 $beatmapset = Beatmapset::factory()->create([
73 'approved' => Beatmapset::STATES['pending'],
74 ]);
75 $beatmap = Beatmap::factory()->create(['beatmapset_id' => $beatmapset->getKey()]);
76 $nominator = User::factory()->withGroup('bng', [$beatmap->mode])->create();
77
78 $beatmapset->update(['user_id' => $nominator->getKey()]);
79
80 $this->actingAsVerified($nominator)
81 ->put(route('beatmapsets.nominate', ['beatmapset' => $beatmapset->getKey(), 'playmodes' => [$beatmap->mode]]))
82 ->assertStatus(403);
83
84 $this->assertSame(0, $beatmapset->beatmapsetNominations()->current()->count());
85 }
86
87 public function testBeatmapsetNominateOwnBeatmap()
88 {
89 $beatmapset = Beatmapset::factory()->create([
90 'approved' => Beatmapset::STATES['pending'],
91 ]);
92 $beatmap = Beatmap::factory()->create(['beatmapset_id' => $beatmapset->getKey()]);
93 $nominator = User::factory()->withGroup('bng', [$beatmap->mode])->create();
94
95 $beatmap->update(['user_id' => $nominator->getKey()]);
96
97 $this->actingAsVerified($nominator)
98 ->put(route('beatmapsets.nominate', ['beatmapset' => $beatmapset->getKey(), 'playmodes' => [$beatmap->mode]]))
99 ->assertStatus(403);
100
101 $this->assertSame(0, $beatmapset->beatmapsetNominations()->current()->count());
102 }
103
104 /**
105 * @dataProvider beatmapsetStatesDataProvider
106 */
107 public function testBeatmapsetUpdateMetadataAsModerator($state)
108 {
109 $owner = User::factory()->create();
110 $beatmapset = Beatmapset::factory()->create([
111 'approved' => Beatmapset::STATES[$state],
112 'user_id' => $owner,
113 ]);
114 $newGenre = Genre::factory()->create();
115 $newLanguage = Language::factory()->create();
116
117 $moderator = User::factory()->withGroup('nat')->create();
118
119 $resultGenreId = $newGenre->getKey();
120 $resultLanguageId = $newLanguage->getKey();
121
122 $this->expectCountChange(fn () => BeatmapsetEvent::count(), 2);
123
124 $this->actingAsVerified($moderator)
125 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
126 'beatmapset' => [
127 'genre_id' => $newGenre->getKey(),
128 'language_id' => $newLanguage->getKey(),
129 ],
130 ])->assertSuccessful();
131
132 $beatmapset->refresh();
133
134 $this->assertSame($resultGenreId, $beatmapset->genre_id);
135 $this->assertSame($resultLanguageId, $beatmapset->language_id);
136 }
137
138 /**
139 * @dataProvider beatmapsetStatesDataProvider
140 */
141 public function testBeatmapsetUpdateMetadataAsOtherUser($state)
142 {
143 $owner = User::factory()->create();
144 $beatmapset = Beatmapset::factory()->create([
145 'approved' => Beatmapset::STATES[$state],
146 'user_id' => $owner,
147 ]);
148 $newGenre = Genre::factory()->create();
149 $newLanguage = Language::factory()->create();
150
151 $resultGenreId = $beatmapset->genre_id;
152 $resultLanguageId = $beatmapset->language_id;
153
154 $this->expectCountChange(fn () => BeatmapsetEvent::count(), 0);
155
156 $user = User::factory()->create();
157
158 $this->actingAsVerified($user)
159 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
160 'beatmapset' => [
161 'genre_id' => $newGenre->getKey(),
162 'language_id' => $newLanguage->getKey(),
163 ],
164 ])->assertStatus(403);
165
166 $beatmapset->refresh();
167
168 $this->assertSame($resultGenreId, $beatmapset->genre_id);
169 $this->assertSame($resultLanguageId, $beatmapset->language_id);
170 }
171
172 /**
173 * @dataProvider dataProviderForTestBeatmapsetUpdateDescriptionAsOwner
174 */
175 public function testBeatmapsetUpdateDescriptionAsOwner(bool $downloadDisabled, ?string $downloadDisabledUrl, bool $ok)
176 {
177 $beatmapset = Beatmapset::factory()->owner()->withDescription()->create([
178 'download_disabled' => $downloadDisabled,
179 'download_disabled_url' => $downloadDisabledUrl,
180 ]);
181 $owner = $beatmapset->user;
182 $beatmapset->updateDescription('old description', $owner);
183
184 $newDescription = 'new description';
185 $expectedDescription = $ok ? $newDescription : $beatmapset->editableDescription();
186
187 $this->actingAsVerified($owner)
188 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
189 'description' => $newDescription,
190 ])->assertStatus($ok ? 200 : 403);
191
192 $beatmapset->refresh();
193
194 $this->assertSame($expectedDescription, $beatmapset->editableDescription());
195 }
196
197 /**
198 * @dataProvider beatmapsetStatesDataProvider
199 */
200 public function testBeatmapsetUpdateMetadataAsOwner($state)
201 {
202 $ok = in_array($state, ['graveyard', 'wip', 'pending'], true);
203
204 $owner = User::factory()->create();
205 $beatmapset = Beatmapset::factory()->create([
206 'approved' => Beatmapset::STATES[$state],
207 'user_id' => $owner,
208 ]);
209 $newGenre = Genre::factory()->create();
210 $newLanguage = Language::factory()->create();
211
212 $resultGenreId = $ok ? $newGenre->getKey() : $beatmapset->genre_id;
213 $resultLanguageId = $ok ? $newLanguage->getKey() : $beatmapset->language_id;
214
215 $this->expectCountChange(fn () => BeatmapsetEvent::count(), $ok ? 2 : 0);
216
217 $this->actingAsVerified($owner)
218 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
219 'beatmapset' => [
220 'genre_id' => $newGenre->getKey(),
221 'language_id' => $newLanguage->getKey(),
222 ],
223 ])->assertStatus($ok ? 200 : 403);
224
225 $beatmapset->refresh();
226
227 $this->assertSame($resultGenreId, $beatmapset->genre_id);
228 $this->assertSame($resultLanguageId, $beatmapset->language_id);
229 }
230
231 /**
232 * @dataProvider beatmapsetStatesDataProvider
233 */
234 public function testBeatmapsetUpdateMetadataAsProjectLoved(string $state): void
235 {
236 $beatmapset = Beatmapset::factory()->create([
237 'approved' => Beatmapset::STATES[$state],
238 'user_id' => User::factory(),
239 ]);
240 $owner = $beatmapset->user;
241 $newGenre = Genre::factory()->create();
242 $newLanguage = Language::factory()->create();
243
244 if (in_array($state, ['graveyard', 'loved'], true)) {
245 $countChange = 2;
246 $status = 200;
247 $resultGenreId = $newGenre->getKey();
248 $resultLanguageId = $newLanguage->getKey();
249 } else {
250 $countChange = 0;
251 $status = 403;
252 $resultGenreId = $beatmapset->genre_id;
253 $resultLanguageId = $beatmapset->language_id;
254 }
255 $this->expectCountChange(fn () => BeatmapsetEvent::count(), $countChange);
256
257 $user = User::factory()->withGroup('loved')->create();
258
259 $this->actingAsVerified($user)
260 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
261 'beatmapset' => [
262 'genre_id' => $newGenre->getKey(),
263 'language_id' => $newLanguage->getKey(),
264 ],
265 ])->assertStatus($status);
266
267 $beatmapset->refresh();
268
269 $this->assertSame($resultGenreId, $beatmapset->genre_id);
270 $this->assertSame($resultLanguageId, $beatmapset->language_id);
271 }
272
273 /**
274 * @dataProvider dataProviderForTestBeatmapsetUpdateOffset
275 */
276 public function testBeatmapsetUpdateOffset(string $userGroupOrOwner, bool $ok): void
277 {
278 $beatmapset = Beatmapset::factory()->create([
279 'approved' => Beatmapset::STATES['ranked'],
280 'user_id' => User::factory(),
281 ]);
282
283 $user = $userGroupOrOwner === 'owner'
284 ? $beatmapset->user
285 : User::factory()->withGroup($userGroupOrOwner)->create();
286
287 $newOffset = $beatmapset->offset + 25;
288 $expectedOffset = $ok ? $newOffset : $beatmapset->offset;
289
290 $this->expectCountChange(fn () => BeatmapsetEvent::count(), $ok ? 1 : 0);
291
292 $this->actingAsVerified($user)
293 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
294 'beatmapset' => [
295 'offset' => $newOffset,
296 ],
297 ])->assertStatus($ok ? 200 : 403);
298
299 $beatmapset->refresh();
300
301 $this->assertSame($expectedOffset, $beatmapset->offset);
302 }
303
304 /**
305 * @dataProvider dataProviderForTestBeatmapsetUpdateTags
306 */
307 public function testBeatmapsetUpdateTags(string $userGroupOrOwner, bool $ok): void
308 {
309 $beatmapset = Beatmapset::factory()->create([
310 'approved' => Beatmapset::STATES['ranked'],
311 'user_id' => User::factory(),
312 ]);
313
314 $user = $userGroupOrOwner === 'owner'
315 ? $beatmapset->user
316 : User::factory()->withGroup($userGroupOrOwner)->create();
317
318 $newTags = "{$beatmapset->tags} more_tag";
319 $expectedTags = $ok ? $newTags : $beatmapset->tags;
320
321 $this->expectCountChange(fn () => BeatmapsetEvent::count(), $ok ? 1 : 0);
322
323 $this->actingAsVerified($user)
324 ->put(route('beatmapsets.update', ['beatmapset' => $beatmapset->getKey()]), [
325 'beatmapset' => [
326 'tags' => $newTags,
327 ],
328 ])->assertStatus($ok ? 200 : 403);
329
330 $this->assertSame($expectedTags, $beatmapset->fresh()->tags);
331 }
332
333 public static function beatmapsetStatesDataProvider()
334 {
335 return array_map(function ($state) {
336 return [$state];
337 }, array_keys(Beatmapset::STATES));
338 }
339
340 public static function dataProviderForTestBeatmapsetUpdateOffset(): array
341 {
342 return [
343 ['admin', true],
344 ['bng', false],
345 ['default', false],
346 ['gmt', false],
347 ['nat', false],
348 ['owner', false],
349 ];
350 }
351
352 public static function dataProviderForTestBeatmapsetUpdateTags(): array
353 {
354 return [
355 ['admin', true],
356 ['bng', false],
357 ['default', false],
358 ['gmt', true],
359 ['nat', true],
360 ['owner', false],
361 ];
362 }
363
364 public static function dataProviderForTestBeatmapsetUpdateDescriptionAsOwner(): array
365 {
366 return [
367 [false, null, true],
368 [true, null, false],
369 [false, 'https://fail.works/notice', false],
370 [true, 'https://fail.works/notice', false],
371 ];
372 }
373}