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 6declare(strict_types=1); 7 8namespace Tests\Controllers\Chat; 9 10use App\Libraries\UserChannelList; 11use App\Models\Chat\Channel; 12use App\Models\Chat\Message; 13use App\Models\Multiplayer\ScoreLink; 14use App\Models\Multiplayer\UserScoreAggregate; 15use App\Models\User; 16use Illuminate\Testing\AssertableJsonString; 17use Illuminate\Testing\Fluent\AssertableJson; 18use Tests\TestCase; 19 20class ChannelsControllerTest extends TestCase 21{ 22 private User $user; 23 private User $anotherUser; 24 private Channel $pmChannel; 25 private Channel $privateChannel; 26 private Channel $publicChannel; 27 private Message $publicMessage; 28 29 //region GET /chat/channels - Get Channel List 30 public function testChannelIndexWhenGuest() 31 { 32 $this->json('GET', route('api.chat.channels.index')) 33 ->assertStatus(401); 34 } 35 36 public function testChannelIndex() 37 { 38 $this->actAsScopedUser($this->user, ['*']); 39 $this->json('GET', route('api.chat.channels.index')) 40 ->assertStatus(200) 41 ->assertJsonFragment(['channel_id' => $this->publicChannel->channel_id]) 42 ->assertJsonMissing(['channel_id' => $this->privateChannel->channel_id]) 43 ->assertJsonMissing(['channel_id' => $this->pmChannel->channel_id]); 44 } 45 46 //endregion 47 48 //region POST /chat/channels - Create and join channel 49 public function testChannelStoreAnnouncement() 50 { 51 $sender = User::factory()->withGroup('announce')->create(); 52 $users = User::factory()->count(2)->create(); 53 54 $this->actAsScopedUser($sender, ['*']); 55 $this 56 ->json('POST', route('api.chat.channels.store'), [ 57 'channel' => [ 58 'description' => 'really', 59 'name' => 'important stuff', 60 ], 61 'message' => 'announcements!!!', 62 'target_ids' => $users->pluck('user_id')->toArray(), 63 'type' => Channel::TYPES['announce'], 64 ]) 65 ->assertSuccessful() 66 ->assertJson(fn (AssertableJson $json) => $json 67 ->where('type', Channel::TYPES['announce']) 68 ->etc()); 69 } 70 71 public function testChannelStoreInvalid() 72 { 73 $this->actAsScopedUser($this->user, ['*']); 74 $this->json('POST', route('api.chat.channels.store'), [ 75 'type' => Channel::TYPES['public'], 76 ])->assertStatus(422); 77 78 $this->json('POST', route('api.chat.channels.store'), [ 79 'type' => Channel::TYPES['pm'], 80 ])->assertStatus(422); 81 } 82 83 public function testChannelStorePM() 84 { 85 $initialChannels = Channel::count(); 86 87 $this->actAsScopedUser($this->user, ['*']); 88 $this->json('POST', route('api.chat.channels.store'), [ 89 'target_id' => $this->anotherUser->getKey(), 90 'type' => Channel::TYPES['pm'], 91 ])->assertSuccessful() 92 ->assertJsonFragment([ 93 'channel_id' => null, 94 'recent_messages' => [], 95 ]); 96 97 $this->assertSame($initialChannels, Channel::count()); 98 } 99 100 public function testChannelStorePMUserLeft() 101 { 102 $channel = Channel::createPM($this->user, $this->anotherUser); 103 $channel->removeUser($this->user); 104 105 // sanity check 106 $this->getAssertableChannelList($this->user) 107 ->assertMissing(['channel_id' => $channel->getKey()]); 108 109 $this->actAsScopedUser($this->user, ['*']); 110 $this->json('POST', route('api.chat.channels.store'), [ 111 'target_id' => $this->anotherUser->getKey(), 112 'type' => Channel::TYPES['pm'], 113 ])->assertSuccessful() 114 ->assertJsonFragment([ 115 'channel_id' => $channel->getKey(), 116 'recent_messages' => [], 117 'type' => Channel::TYPES['pm'], 118 ]); 119 120 $this->assertTrue($channel->hasUser($this->user)); 121 } 122 123 //endregion 124 125 /** 126 * @dataProvider dataProvider 127 */ 128 public function testChannelJoin($type, $success) 129 { 130 $channel = Channel::factory()->type($type)->create(); 131 $status = $success ? 200 : 403; 132 133 $this->actAsScopedUser($this->user, ['*']); 134 135 $this->getAssertableChannelList($this->user) 136 ->assertMissing(['channel_id' => $channel->getKey()]); 137 138 // join channel 139 $request = $this->json('PUT', route('api.chat.channels.join', [ 140 'channel' => $channel->getKey(), 141 'user' => $this->user->getKey(), 142 ]))->assertStatus($status); 143 144 if ($success) { 145 $request->assertJsonFragment(['channel_id' => $channel->getKey()]); 146 147 // ensure now in channel 148 $this->getAssertableChannelList($this->user) 149 ->assertFragment(['channel_id' => $channel->getKey()]); 150 } 151 } 152 153 //region PUT /chat/channels/[channel_id]/users/[user_id] - Join Channel (public) 154 public function testChannelJoinPublicWhenGuest() // fail 155 { 156 $this->json('PUT', route('api.chat.channels.join', [ 157 'channel' => $this->publicChannel->channel_id, 158 'user' => $this->user->user_id, 159 ])) 160 ->assertStatus(401); 161 } 162 163 public function testChannelJoinPublicWhenDifferentUser() // fail 164 { 165 $this->actAsScopedUser($this->user, ['*']); 166 $this->json('PUT', route('api.chat.channels.join', [ 167 'channel' => $this->publicChannel->channel_id, 168 'user' => $this->anotherUser->user_id, 169 ])) 170 ->assertStatus(403); 171 } 172 173 public function testChannelJoinPM() // fail 174 { 175 $this->actAsScopedUser($this->user, ['*']); 176 $this->json('PUT', route('api.chat.channels.join', [ 177 'channel' => $this->pmChannel->channel_id, 178 'user' => $this->user->user_id, 179 ])) 180 ->assertStatus(403); 181 } 182 183 public function testChannelJoinMultiplayerWhenNotParticipated() 184 { 185 $scoreLink = ScoreLink::factory()->create(); 186 UserScoreAggregate::lookupOrDefault($scoreLink->user, $scoreLink->playlistItem->room)->recalculate(); 187 188 $this->actAsScopedUser($this->user, ['*']); 189 $request = $this->json('PUT', route('api.chat.channels.join', [ 190 'channel' => $scoreLink->playlistItem->room->channel_id, 191 'user' => $this->user->getKey(), 192 ])); 193 194 $request->assertStatus(403); 195 } 196 197 public function testChannelJoinMultiplayerWhenParticipated() 198 { 199 $scoreLink = ScoreLink::factory()->create(['user_id' => $this->user]); 200 UserScoreAggregate::lookupOrDefault($scoreLink->user, $scoreLink->playlistItem->room)->recalculate(); 201 202 $this->actAsScopedUser($this->user, ['*']); 203 $request = $this->json('PUT', route('api.chat.channels.join', [ 204 'channel' => $scoreLink->playlistItem->room->channel_id, 205 'user' => $this->user->getKey(), 206 ])); 207 208 $request->assertStatus(403); 209 } 210 211 //endregion 212 213 //region PUT /chat/channels/[channel_id]/mark-as-read/[message_id] - Mark Channel as Read 214 public function testChannelMarkAsReadWhenGuest() // fail 215 { 216 $this->json( 217 'PUT', 218 route('api.chat.channels.mark-as-read', [ 219 'channel' => $this->publicChannel->channel_id, 220 'message' => $this->publicMessage->message_id, 221 ]) 222 ) 223 ->assertStatus(401); 224 } 225 226 public function testChannelMarkAsReadWhenUnjoined() // fail 227 { 228 $this->actAsScopedUser($this->user, ['*']); 229 $this->json( 230 'PUT', 231 route('api.chat.channels.mark-as-read', [ 232 'channel' => $this->publicChannel->channel_id, 233 'message' => $this->publicMessage->message_id, 234 ]) 235 ) 236 ->assertStatus(404); 237 } 238 239 public function testChannelMarkAsReadWhenJoined() // success 240 { 241 $this->actAsScopedUser($this->user, ['*']); 242 $this->json('PUT', route('api.chat.channels.join', [ 243 'channel' => $this->publicChannel->channel_id, 244 'user' => $this->user->user_id, 245 ])); 246 247 $this->actAsScopedUser($this->user, ['*']); 248 $this->json( 249 'PUT', 250 route('api.chat.channels.mark-as-read', [ 251 'channel' => $this->publicChannel->channel_id, 252 'message' => $this->publicMessage->message_id, 253 ]) 254 ) 255 ->assertStatus(204); 256 257 $this->getAssertableChannelList($this->user) 258 ->assertPath('0.current_user_attributes.last_read_id', $this->publicMessage->message_id) 259 ->assertFragment([ 260 'channel_id' => $this->publicChannel->channel_id, 261 'last_read_id' => $this->publicMessage->message_id, 262 ]); 263 } 264 265 public function testChannelMarkAsReadBackwards() // success (with no change) 266 { 267 $newerPublicMessage = Message::factory()->create(['channel_id' => $this->publicChannel]); 268 269 $this->actAsScopedUser($this->user, ['*']); 270 $this->json('PUT', route('api.chat.channels.join', [ 271 'channel' => $this->publicChannel->channel_id, 272 'user' => $this->user->user_id, 273 ])); 274 275 // mark as read to $newerPublicMessage->message_id 276 $this->actAsScopedUser($this->user, ['*']); 277 $this->json( 278 'PUT', 279 route('api.chat.channels.mark-as-read', [ 280 'channel' => $this->publicChannel->channel_id, 281 'message' => $newerPublicMessage->message_id, 282 ]) 283 ) 284 ->assertStatus(204); 285 286 $this->getAssertableChannelList($this->user) 287 ->assertPath('0.current_user_attributes.last_read_id', $newerPublicMessage->message_id) 288 ->assertFragment([ 289 'channel_id' => $this->publicChannel->channel_id, 290 'last_read_id' => $newerPublicMessage->message_id, 291 ]); 292 293 // attempt to mark as read to the older $this->publicMessage->message_id 294 $this->actAsScopedUser($this->user, ['*']); 295 $this->json( 296 'PUT', 297 route('api.chat.channels.mark-as-read', [ 298 'channel' => $this->publicChannel->channel_id, 299 'message' => $this->publicMessage->message_id, 300 ]) 301 ) 302 ->assertStatus(204); 303 304 $this->getAssertableChannelList($this->user) 305 ->assertPath('0.current_user_attributes.last_read_id', $newerPublicMessage->message_id) 306 ->assertFragment([ 307 'channel_id' => $this->publicChannel->channel_id, 308 'last_read_id' => $newerPublicMessage->message_id, 309 ]); 310 } 311 312 //endregion 313 314 //region DELETE /chat/channels/[channel_id]/users/[user_id] - Leave Channel 315 /** 316 * @dataProvider dataProvider 317 */ 318 public function testChannelLeave($type, $success) 319 { 320 $channel = Channel::factory()->type($type)->create(); 321 $channel->addUser($this->user); 322 $status = $success ? 204 : 403; 323 324 $this->actAsScopedUser($this->user, ['*']); 325 326 // ensure in channel 327 $this->getAssertableChannelList($this->user) 328 ->assertFragment(['channel_id' => $channel->getKey()]); 329 330 // leave channel 331 $this->json('DELETE', route('api.chat.channels.part', [ 332 'channel' => $channel->channel_id, 333 'user' => $this->user->getKey(), 334 ])) 335 ->assertStatus($status); 336 337 $channelList = $this->getAssertableChannelList($this->user); 338 339 if ($success) { 340 // ensure no longer in channel 341 $channelList->assertMissing(['channel_id' => $channel->getKey()]); 342 } else { 343 // ensure still in channel 344 $channelList->assertFragment(['channel_id' => $channel->getKey()]); 345 } 346 } 347 348 /** 349 * @dataProvider dataProvider 350 */ 351 public function testChannelLeaveWhenNotJoined($type, $success) 352 { 353 $channel = Channel::factory()->type($type)->create(); 354 $status = $success ? 204 : 403; 355 356 $this->actAsScopedUser($this->user, ['*']); 357 358 // ensure not in channel 359 $this->getAssertableChannelList($this->user) 360 ->assertMissing(['channel_id' => $channel->getKey()]); 361 362 // leave channel 363 $this->json('DELETE', route('api.chat.channels.part', [ 364 'channel' => $channel->channel_id, 365 'user' => $this->user->getKey(), 366 ])) 367 ->assertStatus($status); 368 } 369 370 public function testChannelLeavePublicWhenGuest() // fail 371 { 372 $this->json('DELETE', route('api.chat.channels.part', [ 373 'channel' => $this->publicChannel->channel_id, 374 'user' => $this->user->user_id, 375 ])) 376 ->assertStatus(401); 377 } 378 379 //endregion 380 381 public static function dataProvider() 382 { 383 return [ 384 ['private', false], 385 ['public', true], 386 ['tourney', true], 387 ]; 388 } 389 390 protected function setUp(): void 391 { 392 parent::setUp(); 393 394 $this->user = User::factory()->create(); 395 $this->anotherUser = User::factory()->create(); 396 $this->publicChannel = Channel::factory()->type('public')->create(); 397 $this->privateChannel = Channel::factory()->type('private')->create(); 398 $this->pmChannel = Channel::factory()->type('pm')->create(); 399 $this->publicMessage = Message::factory()->create(['channel_id' => $this->publicChannel]); 400 } 401 402 private function getAssertableChannelList(User $user): AssertableJsonString 403 { 404 return new AssertableJsonString((new UserChannelList($user))->get()); 405 } 406}