the browser-facing portion of osu!
at master 9.5 kB view raw
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\OAuth\Client; 9use App\Models\Score\Best\Osu; 10use App\Models\ScoreReplayStats; 11use App\Models\Solo\Score as SoloScore; 12use App\Models\User; 13use App\Models\UserStatistics; 14use Illuminate\Filesystem\Filesystem; 15use Storage; 16use Tests\TestCase; 17 18class ScoresControllerTest extends TestCase 19{ 20 private Osu $score; 21 private SoloScore $soloScore; 22 private User $user; 23 private User $otherUser; 24 25 private static function getLegacyScoreReplayViewCount(Osu $score): int 26 { 27 return $score->replayViewCount()->first()?->play_count ?? 0; 28 } 29 30 private static function getScoreReplayViewCount(SoloScore $score): int 31 { 32 return ScoreReplayStats::find($score->getKey())?->watch_count ?? 0; 33 } 34 35 private static function getUserReplaysWatchedCount(Osu|SoloScore $score): int 36 { 37 $month = format_month_column(new \DateTime()); 38 39 return $score->user->replaysWatchedCounts()->firstWhere('year_month', $month)?->count ?? 0; 40 } 41 42 private static function getUserReplayPopularity(Osu|SoloScore $score): int 43 { 44 return $score->user->statistics($score->getMode(), true)->first()?->replay_popularity ?? 0; 45 } 46 47 public function testDownloadApiSameUser() 48 { 49 $this->expectCountChange(fn () => static::getLegacyScoreReplayViewCount($this->score), 0); 50 $this->expectCountChange(fn () => static::getScoreReplayViewCount($this->soloScore), 0); 51 $this->expectCountChange(fn () => static::getUserReplayPopularity($this->score), 0); 52 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($this->score), 0); 53 54 $this 55 ->actAsPasswordClientUser($this->user) 56 ->json( 57 'GET', 58 route('api.scores.download-legacy', $this->params()) 59 ) 60 ->assertSuccessful(); 61 } 62 63 public function testDownloadApiSoloScoreSameUser() 64 { 65 $soloScore = SoloScore::factory() 66 ->withReplay() 67 ->create(['user_id' => $this->user->getKey()]); 68 69 $this->expectCountChange(fn () => static::getUserReplayPopularity($soloScore), 0); 70 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($soloScore), 0); 71 72 $this 73 ->actAsPasswordClientUser($this->user) 74 ->json( 75 'GET', 76 route('api.scores.download', $soloScore) 77 ) 78 ->assertSuccessful(); 79 } 80 81 public function testDownload() 82 { 83 $this->expectCountChange(fn () => static::getLegacyScoreReplayViewCount($this->score), 0); 84 $this->expectCountChange(fn () => static::getScoreReplayViewCount($this->soloScore), 0); 85 $this->expectCountChange(fn () => static::getUserReplayPopularity($this->score), 0); 86 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($this->score), 0); 87 88 $this 89 ->actingAs($this->otherUser) 90 ->withHeaders(['HTTP_REFERER' => $GLOBALS['cfg']['app']['url'].'/']) 91 ->json( 92 'GET', 93 route('scores.download-legacy', $this->params()) 94 ) 95 ->assertSuccessful(); 96 } 97 98 public function testDownloadApi(): void 99 { 100 $this->expectCountChange(fn () => static::getLegacyScoreReplayViewCount($this->score), 1); 101 $this->expectCountChange(fn () => static::getScoreReplayViewCount($this->soloScore), 1); 102 $this->expectCountChange(fn () => static::getUserReplayPopularity($this->score), 1); 103 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($this->score), 1); 104 105 $this 106 ->actAsPasswordClientUser($this->otherUser) 107 ->json( 108 'GET', 109 route('api.scores.download-legacy', $this->params()) 110 ) 111 ->assertSuccessful(); 112 } 113 114 public function testDownloadApiTwiceNoCountChange(): void 115 { 116 $this 117 ->actAsPasswordClientUser($this->otherUser) 118 ->json( 119 'GET', 120 route('api.scores.download-legacy', $this->params()) 121 ) 122 ->assertSuccessful(); 123 124 $this->expectCountChange(fn () => static::getLegacyScoreReplayViewCount($this->score), 0); 125 $this->expectCountChange(fn () => static::getScoreReplayViewCount($this->soloScore), 0); 126 $this->expectCountChange(fn () => static::getUserReplayPopularity($this->score), 0); 127 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($this->score), 0); 128 129 $this 130 ->actAsPasswordClientUser($this->otherUser) 131 ->json( 132 'GET', 133 route('api.scores.download-legacy', $this->params()) 134 ) 135 ->assertSuccessful(); 136 } 137 138 public function testDownloadSoloScore() 139 { 140 $soloScore = SoloScore::factory() 141 ->withReplay() 142 ->create(['user_id' => $this->user->getKey()]); 143 144 $this->expectCountChange(fn () => static::getUserReplayPopularity($soloScore), 0); 145 $this->expectCountChange(fn () => static::getUserReplaysWatchedCount($soloScore), 0); 146 147 $this 148 ->actingAs($this->otherUser) 149 ->withHeaders(['HTTP_REFERER' => $GLOBALS['cfg']['app']['url'].'/']) 150 ->json( 151 'GET', 152 route('scores.download', $soloScore) 153 ) 154 ->assertSuccessful(); 155 } 156 157 public function testDownloadDeletedBeatmap() 158 { 159 $this->score->beatmap->delete(); 160 161 $this 162 ->actingAs($this->user) 163 ->withHeaders(['HTTP_REFERER' => $GLOBALS['cfg']['app']['url'].'/']) 164 ->get(route('scores.download-legacy', $this->params())) 165 ->assertSuccessful(); 166 } 167 168 public function testDownloadMissingBeatmap() 169 { 170 $this->score->beatmap->forceDelete(); 171 172 $this 173 ->actingAs($this->user) 174 ->withHeaders(['HTTP_REFERER' => $GLOBALS['cfg']['app']['url'].'/']) 175 ->get(route('scores.download-legacy', $this->params())) 176 ->assertStatus(422); 177 } 178 179 public function testDownloadMissingUser() 180 { 181 $this->score->user->delete(); 182 183 $this 184 ->actingAs($this->otherUser) 185 ->withHeaders(['HTTP_REFERER' => $GLOBALS['cfg']['app']['url'].'/']) 186 ->get(route('scores.download-legacy', $this->params())) 187 ->assertStatus(422); 188 } 189 190 public function testDownloadInvalidReferer() 191 { 192 $this 193 ->actingAs($this->user) 194 ->withHeaders(['HTTP_REFERER' => rtrim($GLOBALS['cfg']['app']['url'], '/').'.example.com']) 195 ->json( 196 'GET', 197 route('scores.download-legacy', $this->params()) 198 ) 199 ->assertRedirect(route('scores.show', $this->params())); 200 } 201 202 public function testDownloadNoReferer() 203 { 204 $this 205 ->actingAs($this->user) 206 ->json( 207 'GET', 208 route('scores.download-legacy', $this->params()) 209 ) 210 ->assertRedirect(route('scores.show', $this->params())); 211 } 212 213 public function testDownloadInvalidRuleset() 214 { 215 $this 216 ->actingAs($this->user) 217 ->json( 218 'GET', 219 route('scores.download-legacy', ['rulesetOrScore' => 'nope', 'score' => $this->score->getKey()]) 220 ) 221 ->assertStatus(302); 222 } 223 224 protected function setUp(): void 225 { 226 parent::setUp(); 227 228 // fake all the replay disks 229 $disks = [SoloScore::replayFileDiskName()]; 230 foreach ($GLOBALS['cfg']['filesystems']['disks']['replays'] as $ruleset => $types) { 231 foreach ($types as $type => $_config) { 232 $disks[] = "replays.{$ruleset}.{$type}"; 233 } 234 } 235 foreach ($disks as $disk) { 236 Storage::fake($disk); 237 } 238 239 // Laravel doesn't remove the directory created for fakes and 240 // Storage::fake() removes the files in the directory when called but leaves the directory there. 241 $this->beforeApplicationDestroyed(function () use ($disks) { 242 foreach ($disks as $disk) { 243 $path = storage_path('framework/testing/disks/'.$disk); 244 (new Filesystem())->deleteDirectory($path); 245 } 246 }); 247 248 $this->user = User::factory()->create(); 249 $this->otherUser = User::factory()->create(); 250 251 UserStatistics\Osu::factory()->create(['user_id' => $this->user->user_id]); 252 $this->score = Osu::factory()->withReplay()->create(['user_id' => $this->user->user_id]); 253 $this->soloScore = SoloScore::factory()->create([ 254 'beatmap_id' => $this->score->beatmap_id, 255 'data' => $this->score->data, 256 'legacy_score_id' => $this->score->getKey(), 257 'user_id' => $this->score->user_id, 258 ]); 259 } 260 261 private function actAsPasswordClientUser(User $user): static 262 { 263 $this->actAsScopedUser($user, ['*'], Client::factory()->create(['password_client' => true])); 264 265 return $this; 266 } 267 268 private function params() 269 { 270 return [ 271 'rulesetOrScore' => $this->score->getMode(), 272 'score' => $this->score->getKey(), 273 ]; 274 } 275}