the browser-facing portion of osu!
at master 11 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\Beatmapset; 9use App\Models\Comment; 10use App\Models\Follow; 11use App\Models\Notification; 12use App\Models\User; 13use Tests\TestCase; 14 15class CommentsControllerTest extends TestCase 16{ 17 private $user; 18 private $minPlays; 19 private $beatmapset; 20 private $params; 21 22 /** 23 * @dataProvider pinPermissionsDataProvider 24 */ 25 public function testPin(?string $groupIdentifier, bool $onBeatmapset, bool $asBeatmapsetOwner, bool $asCommentOwner, bool $withPinned, bool $expectAllowed): void 26 { 27 $user = User::factory()->withGroup($groupIdentifier)->create(); 28 $comment = Comment::factory()->create([ 29 'commentable_type' => $onBeatmapset ? 'beatmapset' : 'build', 30 'user_id' => $asCommentOwner ? $user->getKey() : User::factory(), 31 ]); 32 33 if ($asBeatmapsetOwner) { 34 $comment->commentable->update(['user_id' => $user->getKey()]); 35 } 36 37 if ($withPinned) { 38 $comment->commentable->comments()->save(Comment::factory()->make(['pinned' => true])); 39 } 40 41 $this 42 ->actingAsVerified($user) 43 ->post(route('comments.pin', $comment->getKey())) 44 ->assertStatus($expectAllowed ? 200 : 403); 45 46 $this->assertSame($comment->fresh()->pinned, $expectAllowed); 47 } 48 49 public function testPinReply(): void 50 { 51 $comment = Comment::factory()->reply()->create(); 52 $user = User::factory()->withGroup('admin')->create(); 53 54 $this 55 ->actingAsVerified($user) 56 ->post(route('comments.pin', $comment->getKey())) 57 ->assertStatus(422); 58 59 $this->assertFalse($comment->fresh()->pinned); 60 } 61 62 public function testStore() 63 { 64 $this->prepareForStore(); 65 $otherUser = User::factory()->create(); 66 67 $follow = Follow::create([ 68 'notifiable' => $this->beatmapset, 69 'user' => $otherUser, 70 'subtype' => 'comment', 71 ]); 72 73 $previousComments = Comment::count(); 74 $previousNotifications = Notification::count(); 75 76 $this 77 ->be($this->user) 78 ->post(route('comments.store'), $this->params) 79 ->assertSuccessful(); 80 81 $this->assertSame($previousComments + 1, Comment::count()); 82 $this->assertSame($previousNotifications + 1, Notification::count()); 83 } 84 85 public function testStoreDownloadLimitedBeatmapset() 86 { 87 $this->prepareForStore(); 88 $this->beatmapset->update(['download_disabled_url' => 'https://hello.world']); 89 90 $this->expectCountChange(fn () => Comment::count(), 0); 91 92 $this 93 ->be($this->user) 94 ->post(route('comments.store'), $this->params) 95 ->assertStatus(403); 96 } 97 98 public function testStoreNotEnoughPlays() 99 { 100 $this->prepareForStore(); 101 $this->user->statisticsOsu()->update(['playcount' => $this->minPlays - 1]); 102 $previousComments = Comment::count(); 103 104 $this 105 ->be($this->user) 106 ->post(route('comments.store'), $this->params) 107 ->assertStatus(401) 108 ->assertViewIs('users.verify'); 109 110 $this->assertSame($previousComments, Comment::count()); 111 } 112 113 public function testStoreNotEnoughPlaysVerified() 114 { 115 $this->prepareForStore(); 116 $this->user->statisticsOsu()->update(['playcount' => $this->minPlays - 1]); 117 $previousComments = Comment::count(); 118 119 $this 120 ->actingAsVerified($this->user) 121 ->post(route('comments.store'), $this->params) 122 ->assertStatus(200); 123 124 $this->assertSame($previousComments + 1, Comment::count()); 125 } 126 127 public function testStoreGuest() 128 { 129 $this->prepareForStore(); 130 $previousComments = Comment::count(); 131 132 $this 133 ->post(route('comments.store'), $this->params) 134 ->assertStatus(401); 135 136 $this->assertSame($previousComments, Comment::count()); 137 } 138 139 public function testStoreReply() 140 { 141 $this->prepareForStore(); 142 $parent = $this->beatmapset->comments()->create([ 143 'user_id' => $this->user->getKey(), 144 'message' => 'Hello.', 145 ]); 146 147 $params = ['comment' => [ 148 'parent_id' => $parent->getKey(), 149 'message' => 'This is a reply.', 150 ]]; 151 152 $previousComments = $this->beatmapset->comments()->count(); 153 154 $this 155 ->actingAsVerified($this->user) 156 ->post(route('comments.store'), $params) 157 ->assertStatus(200); 158 159 $this->assertSame($previousComments + 1, $this->beatmapset->comments()->count()); 160 } 161 162 public function testStoreReplyDownloadLimitedBeatmapset() 163 { 164 $this->prepareForStore(); 165 $parent = $this->beatmapset->comments()->create([ 166 'user_id' => $this->user->getKey(), 167 'message' => 'Hello.', 168 ]); 169 $this->beatmapset->update(['download_disabled_url' => 'https://hello.world']); 170 171 $params = ['comment' => [ 172 'parent_id' => $parent->getKey(), 173 'message' => 'This is a reply.', 174 ]]; 175 176 $this->expectCountChange(fn () => Comment::count(), 0); 177 178 $this 179 ->actingAsVerified($this->user) 180 ->post(route('comments.store'), $params) 181 ->assertStatus(403); 182 } 183 184 public function testUpdate() 185 { 186 $this->prepareForStore(); 187 $comment = $this->beatmapset->comments()->create([ 188 'user_id' => $this->user->getKey(), 189 'message' => 'Hello.', 190 ]); 191 192 $newMessage = 'Edited.'; 193 $params = ['comment' => [ 194 'message' => $newMessage, 195 ]]; 196 197 $this 198 ->actingAsVerified($this->user) 199 ->put(route('comments.update', $comment), $params) 200 ->assertSuccessful(); 201 202 $this->assertSame($newMessage, $comment->fresh()->message); 203 } 204 205 public function testUpdateDownloadLimitedBeatmapset() 206 { 207 $this->prepareForStore(); 208 $oldMessage = 'Hello.'; 209 $comment = $this->beatmapset->comments()->create([ 210 'user_id' => $this->user->getKey(), 211 'message' => $oldMessage, 212 ]); 213 $this->beatmapset->update(['download_disabled_url' => 'https://hello.world']); 214 215 $params = ['comment' => [ 216 'message' => 'Edited.', 217 ]]; 218 219 $this 220 ->actingAsVerified($this->user) 221 ->put(route('comments.update', $comment), $params) 222 ->assertStatus(403); 223 224 $this->assertSame($oldMessage, $comment->fresh()->message); 225 } 226 227 public function testApiUnauthenticatedUserCanViewIndex() 228 { 229 $this 230 ->json('GET', route('api.comments.index')) 231 ->assertSuccessful(); 232 } 233 234 public function testApiUnauthenticatedUserCanViewComment() 235 { 236 $comment = Comment::factory()->create(); 237 238 $this 239 ->json('GET', route('api.comments.show', ['comment' => $comment->getKey()])) 240 ->assertSuccessful(); 241 } 242 243 /** 244 * @dataProvider apiRequiresAuthenticationDataProvider 245 */ 246 public function testApiRequiresAuthentication($method, $routeName) 247 { 248 $this 249 ->json($method, route("api.{$routeName}", ['comment' => 1])) 250 ->assertUnauthorized(); 251 } 252 253 public static function apiRequiresAuthenticationDataProvider() 254 { 255 return [ 256 ['DELETE', 'comments.vote'], 257 ['POST', 'comments.vote'], 258 ['POST', 'comments.store'], 259 ['PUT', 'comments.update'], 260 ['DELETE', 'comments.destroy'], 261 ]; 262 } 263 264 /** 265 * Data in order: 266 * - User's group identifier 267 * - Whether the commentable is a beatmapset 268 * - Whether the user is the beatmapset's creator 269 * - Whether the user is the comment's creator 270 * - Whether the commentable already has a pinned comment 271 * - Whether pinning should be allowed 272 */ 273 public static function pinPermissionsDataProvider(): array 274 { 275 return [ 276 ['admin', true, true, true, true, true], 277 ['admin', true, true, true, false, true], 278 ['admin', true, true, false, true, true], 279 ['admin', true, true, false, false, true], 280 ['admin', true, false, true, true, true], 281 ['admin', true, false, true, false, true], 282 ['admin', true, false, false, true, true], 283 ['admin', true, false, false, false, true], 284 ['admin', false, false, true, true, true], 285 ['admin', false, false, true, false, true], 286 ['admin', false, false, false, true, true], 287 ['admin', false, false, false, false, true], 288 ['gmt', true, true, true, true, false], 289 ['gmt', true, true, true, false, true], 290 ['gmt', true, true, false, true, false], 291 ['gmt', true, true, false, false, true], 292 ['gmt', true, false, true, true, false], 293 ['gmt', true, false, true, false, true], 294 ['gmt', true, false, false, true, false], 295 ['gmt', true, false, false, false, true], 296 ['gmt', false, false, true, true, false], 297 ['gmt', false, false, true, false, false], 298 ['gmt', false, false, false, true, false], 299 ['gmt', false, false, false, false, false], 300 [null, true, true, true, true, false], 301 [null, true, true, true, false, true], 302 [null, true, true, false, true, false], 303 [null, true, true, false, false, false], 304 [null, true, false, true, true, false], 305 [null, true, false, true, false, false], 306 [null, true, false, false, true, false], 307 [null, true, false, false, false, false], 308 [null, false, false, true, true, false], 309 [null, false, false, true, false, false], 310 [null, false, false, false, true, false], 311 [null, false, false, false, false, false], 312 ]; 313 } 314 315 private function prepareForStore() 316 { 317 config_set('osu.user.post_action_verification', false); 318 $this->minPlays = $GLOBALS['cfg']['osu']['user']['min_plays_for_posting']; 319 320 $this->user = User::factory()->withPlays()->create(); 321 322 $this->beatmapset = Beatmapset::factory()->create(); 323 324 $this->params = ['comment' => [ 325 'commentable_type' => 'beatmapset', 326 'commentable_id' => $this->beatmapset->getKey(), 327 'message' => 'Hello.', 328 ]]; 329 } 330}