the browser-facing portion of osu!
at master 8.4 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\BeatmapDiscussionVote; 12use App\Models\Beatmapset; 13use App\Models\User; 14use Faker; 15use Tests\TestCase; 16 17class BeatmapDiscussionsControllerTest extends TestCase 18{ 19 protected static $faker; 20 21 protected BeatmapDiscussion $discussion; 22 23 public static function setUpBeforeClass(): void 24 { 25 self::$faker = Faker\Factory::create(); 26 } 27 28 /** 29 * @dataProvider putVoteDataProvider 30 */ 31 public function testPutVote(string $beatmapState, int $status, int $change) 32 { 33 $this->discussion->beatmapset->update(['approved' => Beatmapset::STATES[$beatmapState]]); 34 35 $user = User::factory()->create(); 36 37 $currentVotes = BeatmapDiscussionVote::count(); 38 $currentScore = $this->currentScore(); 39 40 $this->putVote($user, '1') 41 ->assertStatus($status); 42 43 $this->assertSame($currentVotes + $change, BeatmapDiscussionVote::count()); 44 $this->assertSame($currentScore + $change, $this->currentScore()); 45 } 46 47 /** 48 * @dataProvider putVoteAgainDataProvider 49 */ 50 public function testPutVoteAgain(string $score, int $change) 51 { 52 $user = User::factory()->create(); 53 54 $this->discussion->vote([ 55 'score' => 1, 56 'user_id' => $user->getKey(), 57 ]); 58 59 $currentVotes = BeatmapDiscussionVote::count(); 60 $currentScore = $this->currentScore(); 61 62 $this->putVote($user, $score) 63 ->assertStatus(200); 64 65 $this->assertSame($currentVotes + $change, BeatmapDiscussionVote::count()); 66 $this->assertSame($currentScore + $change, $this->currentScore()); 67 } 68 69 // can not vote as discussion starter 70 public function testPutVoteSelf() 71 { 72 $user = $this->discussion->user; 73 $currentVotes = BeatmapDiscussionVote::count(); 74 $currentScore = $this->currentScore(); 75 76 $this->putVote($user, '1') 77 ->assertStatus(403); 78 79 $this->assertSame($currentVotes, BeatmapDiscussionVote::count()); 80 $this->assertSame($currentScore, $this->currentScore()); 81 } 82 83 /** 84 * @dataProvider putVoteChangeToDownDataProvider 85 */ 86 public function testPutVoteChangeToDown(?string $group, int $status, int $scoreChange) 87 { 88 $user = User::factory()->withGroup($group)->create(); 89 90 $this->discussion->vote([ 91 'score' => 1, 92 'user_id' => $user->getKey(), 93 ]); 94 95 $currentVotes = BeatmapDiscussionVote::count(); 96 $currentScore = $this->currentScore(); 97 98 $this 99 ->putVote($user, '-1') 100 ->assertStatus($status); 101 102 $this->assertSame($currentVotes, BeatmapDiscussionVote::count()); 103 $this->assertSame($currentScore + $scoreChange, $this->currentScore()); 104 } 105 106 /** 107 * @dataProvider putVoteDownDataProvider 108 */ 109 public function testPutVoteDown(?string $group, int $status, int $voteChange, int $scoreChange) 110 { 111 $user = User::factory()->withGroup($group)->create(); 112 113 $currentVotes = BeatmapDiscussionVote::count(); 114 $currentScore = $this->currentScore(); 115 116 $this 117 ->putVote($user, '-1') 118 ->assertStatus($status); 119 120 $this->assertSame($currentVotes + $voteChange, BeatmapDiscussionVote::count()); 121 $this->assertSame($currentScore + $scoreChange, $this->currentScore()); 122 } 123 124 // posting reviews - fail scenarios ---- 125 126 // guest user 127 public function testPostReviewGuest() 128 { 129 $this 130 ->post(route('beatmapsets.discussion.review', $this->discussion->beatmapset_id)) 131 ->assertUnauthorized(); 132 } 133 134 // invalid document 135 public function testPostReviewDocumentMissing() 136 { 137 $this 138 ->actingAsVerified($this->discussion->user) 139 ->post(route('beatmapsets.discussion.review', $this->discussion->beatmapset_id)) 140 ->assertStatus(422); 141 } 142 143 // posting reviews - success scenario ---- 144 145 // valid document containing issue embeds 146 public function testPostReviewDocumentValidWithIssues() 147 { 148 $user = $this->discussion->user; 149 $discussionCount = BeatmapDiscussion::count(); 150 $discussionPostCount = BeatmapDiscussionPost::count(); 151 $timestampedIssueText = '00:01:234 '.self::$faker->sentence(); 152 $issueText = self::$faker->sentence(); 153 154 $document = json_encode( 155 [ 156 [ 157 'type' => 'embed', 158 'discussion_type' => 'problem', 159 'text' => $timestampedIssueText, 160 'timestamp' => true, 161 'beatmap_id' => $this->discussion->beatmap_id, 162 ], 163 [ 164 'type' => 'embed', 165 'discussion_type' => 'problem', 166 'text' => $issueText, 167 ], 168 ] 169 ); 170 171 $this 172 ->actingAsVerified($user) 173 ->post(route('beatmapsets.discussion.review', $this->discussion->beatmapset_id), [ 174 'document' => $document, 175 ]) 176 ->assertSuccessful() 177 ->assertJsonFragment( 178 [ 179 'user_id' => $user->getKey(), 180 'message' => $timestampedIssueText, 181 ] 182 ) 183 // ensure timestamp was parsed correctly 184 ->assertJsonFragment( 185 [ 186 'timestamp' => 1234, 187 ] 188 ) 189 ->assertJsonFragment( 190 [ 191 'user_id' => $user->getKey(), 192 'message' => $issueText, 193 ] 194 ); 195 196 // ensure 3 discussions/posts are created - one for the review and one for each embedded problem 197 $this->assertSame($discussionCount + 3, BeatmapDiscussion::count()); 198 $this->assertSame($discussionPostCount + 3, BeatmapDiscussionPost::count()); 199 } 200 201 public static function putVoteDataProvider() 202 { 203 return [ 204 ['graveyard', 403, 0], 205 ['wip', 200, 1], 206 ['pending', 200, 1], 207 ['ranked', 403, 0], 208 ['approved', 403, 0], 209 // TODO: qualified; factory the beatmapset with the correct state instead of using update. 210 ['loved', 403, 0], 211 ]; 212 } 213 214 public static function putVoteAgainDataProvider() 215 { 216 return [ 217 'voting again has no effect' => ['1', 0], 218 'voting 0 will remove the vote' => ['0', -1], 219 ]; 220 } 221 222 public static function putVoteChangeToDownDataProvider() 223 { 224 return [ 225 'bng can change to down vote' => ['bng', 200, -2], 226 'regular user can change to down vote' => [null, 200, -2], 227 ]; 228 } 229 230 public static function putVoteDownDataProvider() 231 { 232 return [ 233 'bng can down vote' => ['bng', 200, 1, -1], 234 'regular user can down vote' => [null, 200, 1, -1], 235 ]; 236 } 237 238 protected function setUp(): void 239 { 240 parent::setUp(); 241 242 $mapper = User::factory()->create(); 243 $user = User::factory()->create(); 244 $beatmapset = Beatmapset::factory()->pending()->owner($mapper)->create(); 245 // TODO: adding beatmap to beatmapset should probably copy come attributes. 246 $beatmap = $beatmapset->beatmaps()->save(Beatmap::factory()->make(['user_id' => $mapper])); 247 $this->discussion = BeatmapDiscussion::factory()->timeline()->create([ 248 'beatmapset_id' => $beatmapset, 249 'beatmap_id' => $beatmap, 250 'user_id' => $user, 251 ]); 252 } 253 254 private function currentScore() 255 { 256 return (int) $this->discussion->fresh()->beatmapDiscussionVotes()->sum('score'); 257 } 258 259 private function putVote(?User $user, string $score) 260 { 261 return $this 262 ->actingAsVerified($user) 263 ->put(route('beatmapsets.discussions.vote', $this->discussion), [ 264 'beatmap_discussion_vote' => ['score' => $score], 265 ]); 266 } 267}