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\Solo;
7
8use App\Models\Build;
9use App\Models\ScoreToken;
10use App\Models\Solo\Score;
11use App\Models\User;
12use LaravelRedis;
13use Tests\TestCase;
14
15class ScoresControllerTest extends TestCase
16{
17 public function testStore()
18 {
19 $build = Build::factory()->create(['allow_ranking' => true]);
20 $scoreToken = ScoreToken::factory()->create(['build_id' => $build]);
21
22 $this->expectCountChange(fn () => Score::count(), 1);
23 $this->expectCountChange(fn () => $this->processingQueueCount(), 1);
24 $this->expectCountChange(
25 fn () => \LaravelRedis::llen($GLOBALS['cfg']['osu']['client']['token_queue']),
26 1,
27 );
28
29 $this->withHeaders(['x-token' => static::createClientToken($build)]);
30 $this->actAsScopedUser($scoreToken->user, ['*']);
31
32 $this->json(
33 'PUT',
34 route('api.beatmaps.solo.scores.store', [
35 'beatmap' => $scoreToken->beatmap->getKey(),
36 'token' => $scoreToken->getKey(),
37 ]),
38 [
39 'accuracy' => 1,
40 'max_combo' => 10,
41 'mods' => [
42 ['acronym' => 'DT'],
43 ],
44 'passed' => true,
45 'rank' => 'A',
46 'statistics' => ['Good' => 1],
47 'total_score' => 10,
48 ]
49 )->assertSuccessful()
50 ->assertJsonFragment(['build_id' => $scoreToken->build_id]);
51
52 $score = $scoreToken->fresh()->score;
53 $this->assertNotNull($score);
54 $this->assertSame(1, count($score->data->mods));
55 }
56
57 public function testStoreCompleted()
58 {
59 $scoreToken = ScoreToken::factory()->create();
60 $score = Score::factory()->create(['beatmap_id' => $scoreToken->beatmap_id]);
61 $scoreToken->update(['score_id' => $score->getKey()]);
62
63 $this->expectCountChange(fn () => Score::count(), 0);
64 $this->expectCountChange(fn () => $this->processingQueueCount(), 0);
65
66 $this->actAsScopedUser($scoreToken->user, ['*']);
67
68 $this->json(
69 'PUT',
70 route('api.beatmaps.solo.scores.store', [
71 'beatmap' => $scoreToken->beatmap->getKey(),
72 'token' => $scoreToken->getKey(),
73 ]),
74 [
75 'accuracy' => 1,
76 'max_combo' => 10,
77 'passed' => true,
78 'rank' => 'A',
79 'statistics' => ['Good' => 1],
80 'total_score' => 10,
81 ]
82 )->assertStatus(200);
83
84 $this->assertSame($score->getKey(), $scoreToken->fresh()->score_id);
85 }
86
87 public function testStoreMissingData()
88 {
89 $scoreToken = ScoreToken::factory()->create();
90
91 $this->expectCountChange(fn () => Score::count(), 0);
92
93 $this->actAsScopedUser($scoreToken->user, ['*']);
94
95 $this->json(
96 'PUT',
97 route('api.beatmaps.solo.scores.store', [
98 'beatmap' => $scoreToken->beatmap->getKey(),
99 'token' => $scoreToken->getKey(),
100 ]),
101 [
102 'rank' => 'A',
103 ]
104 )->assertStatus(422);
105 }
106
107 public function testStoreUpdatedBeatmap()
108 {
109 $build = Build::factory()->create(['allow_ranking' => true]);
110 $scoreToken = ScoreToken::factory()->create(['build_id' => $build]);
111 $scoreToken->beatmap->beatmapset->update(['approved_date' => $scoreToken->created_at->addMinutes(5)]);
112
113 $this->expectCountChange(fn () => Score::count(), 0);
114 $this->expectCountChange(fn () => $this->processingQueueCount(), 0);
115 $this->expectCountChange(
116 fn () => \LaravelRedis::llen($GLOBALS['cfg']['osu']['client']['token_queue']),
117 0,
118 );
119
120 $this->withHeaders(['x-token' => static::createClientToken($build)]);
121 $this->actAsScopedUser($scoreToken->user, ['*']);
122
123 $this->json(
124 'PUT',
125 route('api.beatmaps.solo.scores.store', [
126 'beatmap' => $scoreToken->beatmap->getKey(),
127 'token' => $scoreToken->getKey(),
128 ]),
129 [
130 'accuracy' => 1,
131 'max_combo' => 10,
132 'mods' => [
133 ['acronym' => 'DT'],
134 ],
135 'passed' => true,
136 'rank' => 'A',
137 'statistics' => ['Good' => 1],
138 'total_score' => 10,
139 ]
140 )->assertStatus(422);
141
142 $score = $scoreToken->fresh()->score;
143 $this->assertNull($score);
144 }
145
146 public function testStoreWrongUser()
147 {
148 $otherUser = User::factory()->create();
149 $scoreToken = ScoreToken::factory()->create();
150 $this->expectCountChange(fn () => Score::count(), 0);
151
152 $this->actAsScopedUser($otherUser, ['*']);
153
154 $this->json(
155 'PUT',
156 route('api.beatmaps.solo.scores.store', [
157 'beatmap' => $scoreToken->beatmap->getKey(),
158 'token' => $scoreToken->getKey(),
159 ]),
160 [
161 'accuracy' => 1,
162 'max_combo' => 10,
163 'passed' => true,
164 'rank' => 'A',
165 'statistics' => ['Good' => 1],
166 'total_score' => 10,
167 ]
168 )->assertStatus(404);
169 }
170
171 public function tearDown(): void
172 {
173 parent::tearDown();
174
175 static::createApp();
176 LaravelRedis::del($GLOBALS['cfg']['osu']['scores']['processing_queue']);
177 }
178
179 private function processingQueueCount(): int
180 {
181 return LaravelRedis::llen($GLOBALS['cfg']['osu']['scores']['processing_queue']);
182 }
183}