the browser-facing portion of osu!
at master 8.2 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\Models; 9 10use App\Exceptions\InvariantException; 11use App\Exceptions\ValidationException; 12use App\Libraries\MorphMap; 13use App\Models\BeatmapDiscussion; 14use App\Models\BeatmapDiscussionPost; 15use App\Models\Beatmapset; 16use App\Models\Chat\Channel; 17use App\Models\Chat\Message; 18use App\Models\Forum; 19use App\Models\Traits\ReportableInterface; 20use App\Models\User; 21use App\Models\UserReport; 22use Carbon\Carbon; 23use Exception; 24use Tests\TestCase; 25 26class UserReportTest extends TestCase 27{ 28 public static function reportableClasses(): array 29 { 30 $reportables = []; 31 32 foreach (MorphMap::MAP as $class => $_name) { 33 if (isset(class_implements($class)[ReportableInterface::class])) { 34 $reportables[] = [$class]; 35 } 36 } 37 38 // Sanity check to make sure there are models to test. 39 if (count($reportables) === 0) { 40 throw new Exception('No reportables found'); 41 } 42 43 return $reportables; 44 } 45 46 private static function getReportableUser(ReportableInterface $reportable) 47 { 48 return match ($reportable::class) { 49 Message::class => $reportable->sender, 50 User::class => $reportable, 51 default => $reportable->user, 52 }; 53 } 54 55 private static function makeReportable(string $class): ReportableInterface 56 { 57 $modelFactory = $class::factory(); 58 $userColumn = 'user_id'; 59 60 if ($class === Beatmapset::class) { 61 $modelFactory = $modelFactory->pending(); 62 } 63 64 if ($class === BeatmapDiscussionPost::class) { 65 $modelFactory = $modelFactory->state([ 66 'beatmap_discussion_id' => BeatmapDiscussion::factory()->general()->state([ 67 'beatmapset_id' => Beatmapset::factory(), 68 ]), 69 ]); 70 } 71 72 if ($class === Forum\Post::class) { 73 $userColumn = 'poster_id'; 74 } 75 76 if ($class === Message::class) { 77 $modelFactory = $modelFactory->state([ 78 'channel_id' => Channel::factory()->type('public'), 79 ]); 80 } 81 82 return $class === User::class 83 ? $modelFactory->create() 84 : $modelFactory->create([$userColumn => User::factory()]); 85 } 86 87 private static function reportParams(array $additionalParams = []): array 88 { 89 return array_merge([ 90 'comments' => 'some comment', 91 ], $additionalParams); 92 } 93 94 private User $reporter; 95 96 /** 97 * @dataProvider reportableClasses 98 */ 99 public function testCannotReportOwnThing(string $class) 100 { 101 $reportable = static::makeReportable($class); 102 103 $this->expectException(ValidationException::class); 104 $reportable->reportBy(static::getReportableUser($reportable), static::reportParams()); 105 } 106 107 public function testCannotReportScoreableBeatmapset() 108 { 109 $beatmapset = Beatmapset::factory()->qualified()->create(); 110 $reporter = User::factory()->create(); 111 112 $this->expectException(ValidationException::class); 113 $beatmapset->reportBy($reporter, static::reportParams()); 114 } 115 116 public function testCannotReportIfNotInChannel() 117 { 118 $channel = Channel::factory()->type('pm')->create(); 119 $message = Message::factory()->create(['channel_id' => $channel, 'user_id' => $channel->users()->first()]); 120 $reporter = User::factory()->create(); 121 122 $this->expectException(ValidationException::class); 123 $message->reportBy($reporter, static::reportParams()); 124 } 125 126 /** 127 * @dataProvider reportableClasses 128 */ 129 public function testInvalidReason(string $class) 130 { 131 $reportable = static::makeReportable($class); 132 $reporter = User::factory()->create(); 133 134 $this->expectException(ValidationException::class); 135 136 $reportable->reportBy($reporter, static::reportParams([ 137 'reason' => 'NotAValidReason', 138 ])); 139 } 140 141 /** 142 * @dataProvider reportableClasses 143 */ 144 public function testNoComments(string $class): void 145 { 146 $reportable = static::makeReportable($class); 147 $reporter = User::factory()->create(); 148 149 if ($class === Message::class) { 150 $this->expectCountChange(fn () => UserReport::count(), 1); 151 } else { 152 $this->expectException(ValidationException::class); 153 } 154 $reportable->reportBy($reporter, static::reportParams([ 155 'comments' => null, 156 ])); 157 } 158 159 /** 160 * @dataProvider reportableClasses 161 */ 162 public function testNoCommentsReasonOther(string $class): void 163 { 164 $reportable = static::makeReportable($class); 165 $reporter = User::factory()->create(); 166 167 $this->expectException(ValidationException::class); 168 $reportable->reportBy($reporter, static::reportParams([ 169 'comments' => null, 170 'reason' => 'Other', 171 ])); 172 } 173 174 /** 175 * @dataProvider reportableClasses 176 */ 177 public function testReportableInstance(string $class) 178 { 179 $reportable = static::makeReportable($class); 180 $reporter = User::factory()->create(); 181 182 $query = UserReport::whereMorphedTo('reportable', $reportable); 183 $this->expectCountChange(fn () => $query->count(), 1, 'reportable query'); 184 $this->expectCountChange(fn () => $reporter->fresh()->reportsMade->count(), 1, 'reportsMade accessor'); 185 $this->expectCountChange(fn () => $reporter->reportsMade()->count(), 1, 'reportsMade query'); 186 $this->expectCountChange(fn () => $reportable->fresh()->reportedIn->count(), 1, 'reportedIn accessor'); 187 $this->expectCountChange(fn () => $reportable->reportedIn()->count(), 1, 'reportedIn query'); 188 189 $report = $reportable->reportBy($reporter, static::reportParams()); 190 if ($reportable instanceof BestModel) { 191 $this->assertSame($reportable->getKey(), $report->score_id); 192 } 193 $reportableUserId = $reportable instanceof Forum\Post 194 ? $reportable->poster_id 195 : $reportable->user_id; 196 $this->assertSame($reportableUserId, $report->user_id); 197 $this->assertTrue($report->reportable->is($reportable)); 198 } 199 200 /** 201 * @dataProvider reportableClasses 202 */ 203 public function testReportableNotificationEndpoint(string $class): void 204 { 205 $reportable = static::makeReportable($class); 206 $reporter = User::factory()->create(); 207 208 $report = $reportable->reportBy($reporter, static::reportParams()); 209 210 $report->routeNotificationForSlack(null); 211 212 $this->assertTrue(true, 'should not fail getting notification routing url'); 213 } 214 215 public function testReportingAgainAfterAWhile(): void 216 { 217 $reportable = static::makeReportable(User::class); 218 $reporter = User::factory()->create(); 219 220 $oldReport = $reportable->reportBy($reporter, static::reportParams([ 221 'comments' => 'test', 222 ])); 223 $oldReport->update(['timestamp' => Carbon::now()->subYears(1)]); 224 225 $this->expectCountChange(fn () => $reportable->fresh()->reportedIn()->count(), 1); 226 227 $reportable->reportBy($reporter, static::reportParams([ 228 'comments' => 'test', 229 ])); 230 } 231 232 public function testReportingAgainImmediate(): void 233 { 234 $reportable = static::makeReportable(User::class); 235 $reporter = User::factory()->create(); 236 237 $oldReport = $reportable->reportBy($reporter, static::reportParams([ 238 'comments' => 'test', 239 ])); 240 $oldReport->update(['timestamp' => Carbon::now()->subMinute(1)]); 241 242 $this->expectCountChange(fn () => $reportable->fresh()->reportedIn()->count(), 0); 243 244 $this->expectExceptionCallable(function () use ($reportable, $reporter) { 245 $reportable->reportBy($reporter, static::reportParams([ 246 'comments' => 'test', 247 ])); 248 }, InvariantException::class, osu_trans('errors.user_report.recently_reported')); 249 } 250}