the browser-facing portion of osu!
at master 14 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\Beatmap; 9use App\Models\BeatmapDiscussion; 10use App\Models\BeatmapDiscussionPost; 11use App\Models\Beatmapset; 12use App\Models\User; 13use Illuminate\Support\Facades\Event; 14use Tests\TestCase; 15 16class BeatmapDiscussionPostsControllerTest extends TestCase 17{ 18 private Beatmap $beatmap; 19 private BeatmapDiscussion $beatmapDiscussion; 20 private BeatmapDiscussionPost $beatmapDiscussionPost; 21 private Beatmapset $beatmapset; 22 private User $mapper; 23 private User $user; 24 25 public function testPostStoreNewDiscussionInactiveBeatmapset() 26 { 27 $beatmapset = Beatmapset::factory()->owner()->inactive()->create(); 28 29 $this 30 ->actingAsVerified($this->user) 31 ->post(route('beatmapsets.discussions.posts.store'), $this->makeBeatmapsetDiscussionPostParams($beatmapset, 'praise')) 32 ->assertStatus(404); 33 } 34 35 public function testPostUpdate() 36 { 37 $beatmapDiscussionPost = BeatmapDiscussionPost::factory()->create([ 38 'beatmap_discussion_id' => $this->beatmapDiscussion->id, 39 'user_id' => $this->user->user_id, 40 ]); 41 42 $initialMessage = $beatmapDiscussionPost->message; 43 $editedMessage = "{$initialMessage} Edited"; 44 45 $otherUser = User::factory()->create(); 46 47 // invalid user 48 $this 49 ->putPost($editedMessage, $beatmapDiscussionPost, $otherUser) 50 ->assertStatus(403); 51 52 $beatmapDiscussionPost = $beatmapDiscussionPost->fresh(); 53 54 $this->assertSame($initialMessage, $beatmapDiscussionPost->message); 55 56 // correct user 57 $this 58 ->putPost($editedMessage, $beatmapDiscussionPost, $this->user) 59 ->assertStatus(200); 60 61 $beatmapDiscussionPost = $beatmapDiscussionPost->fresh(); 62 63 $this->assertSame($editedMessage, $beatmapDiscussionPost->message); 64 } 65 66 public function testPostUpdateNotLoggedIn() 67 { 68 $post = BeatmapDiscussionPost::factory()->create([ 69 'beatmap_discussion_id' => $this->beatmapDiscussion, 70 'user_id' => $this->user, 71 ]); 72 $initialMessage = $post->message; 73 74 $this->putPost('', $post) 75 ->assertViewIs('users.login') 76 ->assertStatus(401); 77 78 $this->assertSame($initialMessage, $post->fresh()->message); 79 } 80 81 public function testPostUpdateWhenBeatmapsetDiscussionIsLocked() 82 { 83 $reply = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 84 BeatmapDiscussionPost::factory()->timeline()->make([ 85 'user_id' => $this->user, 86 ]) 87 ); 88 $message = $reply->message; 89 90 $this->beatmapset->update(['discussion_locked' => true]); 91 92 $this->putPost("{$message} edited", $reply, $this->user)->assertStatus(403); 93 $this->assertSame($message, $reply->fresh()->message); 94 } 95 96 public function testPostUpdateWhenDiscussionResolved() 97 { 98 // reply made before resolve 99 $reply1 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 100 BeatmapDiscussionPost::factory()->timeline()->make([ 101 'user_id' => $this->user, 102 ]) 103 ); 104 $message1 = $reply1->message; 105 106 $this->postResolveDiscussion(true, $this->user); 107 108 // reply made after resolve 109 $reply2 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 110 BeatmapDiscussionPost::factory()->timeline()->make([ 111 'user_id' => $this->user, 112 ]) 113 ); 114 $message2 = $reply2->message; 115 116 $this->putPost("{$message1} edited", $reply1, $this->user)->assertStatus(403); 117 $this->putPost("{$message2} edited", $reply2, $this->user)->assertSuccessful(); 118 $this->assertSame($message1, $reply1->fresh()->message); 119 $this->assertSame("{$message2} edited", $reply2->fresh()->message); 120 } 121 122 public function testPostUpdateWhenDiscussionReResolved() 123 { 124 // reply made before resolve 125 $reply1 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 126 BeatmapDiscussionPost::factory()->timeline()->make([ 127 'user_id' => $this->user, 128 ]) 129 ); 130 $message1 = $reply1->message; 131 132 $this->postResolveDiscussion(true, $this->user); 133 $this->postResolveDiscussion(false, $this->user); 134 135 // still should not be able to edit reply made before first resolve. 136 $this->putPost("{$message1} edited", $reply1, $this->user)->assertStatus(403); 137 $this->assertSame($message1, $reply1->fresh()->message); 138 139 // reply made after resolve 140 $reply2 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 141 BeatmapDiscussionPost::factory()->timeline()->make([ 142 'user_id' => $this->user, 143 ]) 144 ); 145 $message2 = $reply2->message; 146 147 $this->postResolveDiscussion(true, $this->user); 148 149 // should not be able to edit either reply. 150 $this->putPost("{$message1} edited", $reply1, $this->user)->assertStatus(403); 151 $this->putPost("{$message2} edited", $reply2, $this->user)->assertStatus(403); 152 $this->assertSame($message1, $reply1->fresh()->message); 153 $this->assertSame($message2, $reply2->fresh()->message); 154 } 155 156 public function testStartingPostUpdate() 157 { 158 $post = $this->beatmapDiscussionPost; 159 160 $previousTimestamp = $post->beatmapDiscussion->timestamp; 161 162 // removing timestamp isn't allowed 163 $this 164 ->putPost('Missing timestamp.', $post, $this->user) 165 ->assertStatus(422); 166 167 $post = $post->fresh(); 168 $this->assertSame($previousTimestamp, $post->beatmapDiscussion->timestamp); 169 170 $newTimestamp = $post->beatmapDiscussion->beatmap->total_length * 1000; 171 $newTimestampString = beatmap_timestamp_format($newTimestamp); 172 173 // changing timestamp is allowed 174 $this 175 ->actingAs($this->user) 176 ->put(route('beatmapsets.discussions.posts.update', $post->id), [ 177 'beatmap_discussion_post' => [ 178 'message' => "{$newTimestampString} Edited timestamp.", 179 ], 180 ]) 181 ->assertStatus(200); 182 183 $post = $post->fresh(); 184 $this->assertSame($newTimestamp, $post->beatmapDiscussion->timestamp); 185 } 186 187 public function testPostDestroy() 188 { 189 $reply = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 190 BeatmapDiscussionPost::factory()->timeline()->make([ 191 'user_id' => $this->user, 192 ]) 193 ); 194 195 $this->deletePost($reply, $this->user)->assertStatus(200); 196 $this->assertTrue($reply->fresh()->trashed()); 197 } 198 199 public function testPostDestroyNotLoggedIn() 200 { 201 $reply = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 202 BeatmapDiscussionPost::factory()->timeline()->make([ 203 'user_id' => $this->user, 204 ]) 205 ); 206 207 $this->deletePost($reply) 208 ->assertViewIs('users.login') 209 ->assertStatus(401); 210 211 $this->assertFalse($reply->fresh()->trashed()); 212 } 213 214 public function testPostDestroyWhenBeatmapsetDiscussionIsLocked() 215 { 216 $reply = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 217 BeatmapDiscussionPost::factory()->timeline()->make([ 218 'user_id' => $this->user, 219 ]) 220 ); 221 222 $this->beatmapset->update(['discussion_locked' => true]); 223 224 $this->deletePost($reply, $this->user)->assertStatus(403); 225 $this->assertFalse($reply->fresh()->trashed()); 226 } 227 228 public function testPostDestroyWhenDiscussionResolved() 229 { 230 // reply made before resolve 231 $reply1 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 232 BeatmapDiscussionPost::factory()->timeline()->make([ 233 'user_id' => $this->user, 234 ]) 235 ); 236 237 $this->postResolveDiscussion(true, $this->user); 238 239 // reply made after resolve 240 $reply2 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 241 BeatmapDiscussionPost::factory()->timeline()->make([ 242 'user_id' => $this->user, 243 ]) 244 ); 245 246 $this->deletePost($reply1, $this->user)->assertStatus(403); 247 $this->deletePost($reply2, $this->user)->assertSuccessful(); 248 $this->assertFalse($reply1->fresh()->trashed()); 249 $this->assertTrue($reply2->fresh()->trashed()); 250 } 251 252 public function testPostDestroyWhenDiscussionReResolved() 253 { 254 // reply made before resolve 255 $reply1 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 256 BeatmapDiscussionPost::factory()->timeline()->make([ 257 'user_id' => $this->user, 258 ]) 259 ); 260 261 $this->postResolveDiscussion(true, $this->user); 262 $this->postResolveDiscussion(false, $this->user); 263 264 // still should not be able to delete reply made before first resolve. 265 $this->deletePost($reply1, $this->user)->assertStatus(403); 266 $this->assertFalse($reply1->fresh()->trashed()); 267 268 // reply made after resolve 269 $reply2 = $this->beatmapDiscussion->beatmapDiscussionPosts()->save( 270 BeatmapDiscussionPost::factory()->timeline()->make([ 271 'user_id' => $this->user, 272 ]) 273 ); 274 275 $this->postResolveDiscussion(true, $this->user); 276 277 // should not be able to delete either reply. 278 $this->deletePost($reply1, $this->user)->assertStatus(403); 279 $this->deletePost($reply2, $this->user)->assertStatus(403); 280 $this->assertFalse($reply1->fresh()->trashed()); 281 $this->assertFalse($reply2->fresh()->trashed()); 282 } 283 284 public function testPostWithoutResolveFlag() 285 { 286 $this->beatmapDiscussion->update([ 287 'resolved' => false, 288 ]); 289 290 $otherUser = User::factory()->withPlays()->create(); 291 292 foreach ([$this->user, $otherUser] as $user) { 293 $lastDiscussionPosts = BeatmapDiscussionPost::count(); 294 295 $this 296 ->postDiscussionWithoutResolveFlag($user) 297 ->assertStatus(200); 298 299 // No resolve change, therefore no system posts 300 $this->assertSame($lastDiscussionPosts + 1, BeatmapDiscussionPost::count()); 301 // Should stay unresolved. 302 $this->assertSame(false, $this->beatmapDiscussion->fresh()->resolved); 303 } 304 305 $this 306 ->postResolveDiscussion(true, $this->user) 307 ->assertStatus(200); 308 309 foreach ([$this->user, $otherUser] as $user) { 310 $lastDiscussionPosts = BeatmapDiscussionPost::count(); 311 312 $this 313 ->postDiscussionWithoutResolveFlag($user) 314 ->assertStatus(200); 315 316 $this->assertSame($lastDiscussionPosts + 1, BeatmapDiscussionPost::count()); 317 // Should stay resolved now. 318 $this->assertSame(true, $this->beatmapDiscussion->fresh()->resolved); 319 } 320 } 321 322 protected function setUp(): void 323 { 324 parent::setUp(); 325 326 Event::fake(); 327 328 $this->mapper = User::factory()->withPlays()->create(); 329 $this->user = User::factory()->withPlays()->create(); 330 331 $this->beatmapset = Beatmapset::factory()->owner($this->mapper)->create(); 332 $this->beatmap = $this->beatmapset->beatmaps()->save(Beatmap::factory()->make([ 333 'user_id' => $this->mapper->getKey(), 334 ])); 335 $this->beatmapDiscussion = BeatmapDiscussion::factory()->timeline()->create([ 336 'beatmapset_id' => $this->beatmapset, 337 'beatmap_id' => $this->beatmap, 338 'user_id' => $this->user, 339 ]); 340 $post = BeatmapDiscussionPost::factory()->timeline()->make([ 341 'user_id' => $this->user, 342 ]); 343 $this->beatmapDiscussionPost = $this->beatmapDiscussion->beatmapDiscussionPosts()->save($post); 344 } 345 346 private function deletePost(BeatmapDiscussionPost $post, ?User $user = null) 347 { 348 return ($user === null ? $this : $this->actingAsVerified($user)) 349 ->delete(route('beatmapsets.discussions.posts.destroy', $post->id)); 350 } 351 352 private function postResolveDiscussion(bool $resolved, User $user) 353 { 354 return $this 355 ->actingAsVerified($user) 356 ->post(route('beatmapsets.discussions.posts.store'), [ 357 'beatmap_discussion_id' => $this->beatmapDiscussion->id, 358 'beatmap_discussion' => [ 359 'resolved' => $resolved, 360 ], 361 'beatmap_discussion_post' => [ 362 'message' => 'Hello', 363 ], 364 ]); 365 } 366 367 private function postDiscussionWithoutResolveFlag(User $user) 368 { 369 return $this 370 ->actingAsVerified($user) 371 ->post(route('beatmapsets.discussions.posts.store'), [ 372 'beatmap_discussion_id' => $this->beatmapDiscussion->id, 373 'beatmap_discussion' => [], 374 'beatmap_discussion_post' => [ 375 'message' => 'Hello', 376 ], 377 ]); 378 } 379 380 private function putPost(string $message, BeatmapDiscussionPost $post, ?User $user = null) 381 { 382 return ($user === null ? $this : $this->actingAsVerified($user)) 383 ->put(route('beatmapsets.discussions.posts.update', $post->id), [ 384 'beatmap_discussion_post' => [ 385 'message' => $message, 386 ], 387 ]); 388 } 389}