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 6declare(strict_types=1); 7 8namespace Tests\Models; 9 10use App\Jobs\CheckBeatmapsetCovers; 11use App\Libraries\BeatmapsetDiscussion\Reply; 12use App\Models\Beatmap; 13use App\Models\BeatmapDiscussion; 14use App\Models\Beatmapset; 15use App\Models\Genre; 16use App\Models\Language; 17use App\Models\User; 18use Bus; 19use Carbon\CarbonImmutable; 20use Database\Factories\BeatmapsetFactory; 21use Tests\TestCase; 22 23class BeatmapsetRequalifyTest extends TestCase 24{ 25 private const DISQUALIFIED_INTERVAL = 86400; 26 27 // User for disqualifying and resolving discussion. 28 private User $user; 29 30 public function testDoesNotResetQueue() 31 { 32 $disqualifiedDate = CarbonImmutable::now()->subDays(1); 33 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 34 35 $this->travelTo($qualifiedDate); 36 37 $beatmapset = $this->beatmapsetFactory()->create(); 38 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 39 40 // sanity 41 $this->assertSame(0, $beatmapset->previous_queue_duration); 42 $this->assertEquals($qualifiedDate, $beatmapset->approved_date); 43 44 $this->travelTo($disqualifiedDate); 45 46 $discussion = $this->disqualifyOrResetNominations($beatmapset); 47 $beatmapset = $beatmapset->fresh(); 48 $this->assertDiffUpToOneSecond(static::DISQUALIFIED_INTERVAL, $beatmapset->previous_queue_duration); 49 $this->assertNull($beatmapset->queued_at); 50 51 $this->travelBack(); 52 53 $this->resolveDiscussionAndNominate($discussion, $nominators); 54 $beatmapset = $beatmapset->fresh(); 55 56 $this->assertTrue($beatmapset->isQualified()); 57 $this->assertEquals($beatmapset->approved_date->toImmutable()->subSeconds(static::DISQUALIFIED_INTERVAL), $beatmapset->queued_at); 58 } 59 60 public function testDifferentNominatorResetsQueue() 61 { 62 $disqualifiedDate = CarbonImmutable::now()->subDays(1); 63 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 64 65 $this->travelTo($qualifiedDate); 66 67 $beatmapset = $this->beatmapsetFactory()->create(); 68 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 69 // replace 1 nominator with a different one. 70 $nominators[0] = $this->user; 71 72 $this->travelTo($disqualifiedDate); 73 74 $discussion = $this->disqualifyOrResetNominations($beatmapset); 75 $beatmapset = $beatmapset->fresh(); 76 77 $this->travelBack(); 78 79 $this->resolveDiscussionAndNominate($discussion, $nominators); 80 $beatmapset = $beatmapset->fresh(); 81 82 $this->assertTrue($beatmapset->isQualified()); 83 $this->assertEqualsUpToOneSecond(CarbonImmutable::now(), $beatmapset->queued_at); 84 $this->assertEquals($beatmapset->approved_date, $beatmapset->queued_at); 85 } 86 87 public function testDifferentNominatorBeforeNominationResetDoesNotResetQueue() 88 { 89 $disqualifiedDate = CarbonImmutable::now()->subDays(2); 90 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 91 92 $this->travelTo($qualifiedDate); 93 94 $beatmapset = $this->beatmapsetFactory()->create(); 95 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 96 97 $this->travelTo($disqualifiedDate); 98 99 // disqualify 100 $discussion = $this->disqualifyOrResetNominations($beatmapset); 101 $beatmapset = $beatmapset->fresh(); 102 103 // test nomination reset 104 $this->travelTo($disqualifiedDate->addSeconds(60)); 105 $this->resolveDiscussionAndNominate($discussion, [$this->user]); 106 $discussion = $this->disqualifyOrResetnominations($beatmapset->fresh()); 107 $beatmapset = $beatmapset->fresh(); 108 109 $this->travelBack(); 110 111 $this->resolveDiscussionAndNominate($discussion, $nominators); 112 $beatmapset = $beatmapset->fresh(); 113 114 $this->assertTrue($beatmapset->isQualified()); 115 $this->assertEquals($beatmapset->approved_date->toImmutable()->subSeconds(static::DISQUALIFIED_INTERVAL), $beatmapset->queued_at); 116 } 117 118 // tests nominators from previous qualification are considered as different nominators. 119 public function testNominatorFromPriorQualificationResetsQueue() 120 { 121 $disqualifiedDate = CarbonImmutable::now()->subDays(1); 122 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 123 124 $this->travelTo($qualifiedDate); 125 126 $beatmapset = $this->beatmapsetFactory()->create(); 127 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 128 129 $this->travelTo($disqualifiedDate); 130 131 $discussion = $this->disqualifyOrResetNominations($beatmapset); 132 $beatmapset = $beatmapset->fresh(); 133 134 // second qualification 135 $this->travelTo($disqualifiedDate->addSeconds(60)); 136 137 $newNominators = User::factory()->withGroup('nat')->count($GLOBALS['cfg']['osu']['beatmapset']['required_nominations'])->create(); 138 $this->resolveDiscussionAndNominate($discussion, $newNominators); 139 $beatmapset = $beatmapset->fresh(); 140 141 $this->assertTrue($beatmapset->isQualified()); 142 $this->assertEqualsUpToOneSecond(CarbonImmutable::now(), $beatmapset->queued_at); 143 $this->assertEquals($beatmapset->approved_date, $beatmapset->queued_at); 144 145 // second disqualification 146 $discussion = $this->disqualifyOrResetNominations($beatmapset); 147 $beatmapset = $beatmapset->fresh(); 148 $this->assertTrue($beatmapset->isPending()); 149 150 $this->travelBack(); 151 152 $this->resolveDiscussionAndNominate($discussion, $nominators); 153 $beatmapset = $beatmapset->fresh(); 154 155 $this->assertTrue($beatmapset->isQualified()); 156 $this->assertEqualsUpToOneSecond(CarbonImmutable::now(), $beatmapset->queued_at); 157 $this->assertEquals($beatmapset->approved_date, $beatmapset->queued_at); 158 } 159 160 public function testNominatorFromRecentQualificationDoesNotResetQueue() 161 { 162 $disqualifiedDate = CarbonImmutable::now()->subDays(1); 163 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 164 165 $this->travelTo($qualifiedDate); 166 167 $beatmapset = $this->beatmapsetFactory()->create(); 168 169 $this->travelTo($disqualifiedDate); 170 171 $discussion = $this->disqualifyOrResetNominations($beatmapset); 172 $beatmapset = $beatmapset->fresh(); 173 174 // second qualification 175 $this->travelTo($disqualifiedDate->addSeconds(60)); 176 177 $newNominators = User::factory()->withGroup('nat')->count($GLOBALS['cfg']['osu']['beatmapset']['required_nominations'])->create(); 178 $this->resolveDiscussionAndNominate($discussion, $newNominators); 179 $beatmapset = $beatmapset->fresh(); 180 181 $this->assertTrue($beatmapset->isQualified()); 182 $this->assertEqualsUpToOneSecond(CarbonImmutable::now(), $beatmapset->queued_at); 183 $this->assertEquals($beatmapset->approved_date, $beatmapset->queued_at); 184 $previousQueueDuration = $beatmapset->previous_queue_duration; 185 186 // second disqualification 187 $discussion = $this->disqualifyOrResetNominations($beatmapset); 188 $beatmapset = $beatmapset->fresh(); 189 $this->assertTrue($beatmapset->isPending()); 190 191 $this->travelBack(); 192 193 $this->resolveDiscussionAndNominate($discussion, $newNominators); 194 $beatmapset = $beatmapset->fresh(); 195 196 // queue should not reset. 197 $this->assertTrue($beatmapset->isQualified()); 198 $this->assertDiffUpToOneSecond($previousQueueDuration, CarbonImmutable::now()->getTimestamp() - $beatmapset->queued_at->getTimestamp()); 199 } 200 201 public function testNewDifficultyAddedResetsQueue() 202 { 203 $disqualifiedDate = CarbonImmutable::now()->subDays(1); 204 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 205 206 $this->travelTo($qualifiedDate); 207 208 $beatmapset = $this->beatmapsetFactory()->create(); 209 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 210 211 $this->travelTo($disqualifiedDate); 212 213 $discussion = $this->disqualifyOrResetNominations($beatmapset); 214 $beatmapset = $beatmapset->fresh(); 215 216 $this->travelBack(); 217 218 $beatmapset->beatmaps()->save(Beatmap::factory()->ruleset('osu')->make()); 219 220 $this->resolveDiscussionAndNominate($discussion, $nominators); 221 $beatmapset = $beatmapset->fresh(); 222 223 $this->assertTrue($beatmapset->isQualified()); 224 $this->assertEqualsUpToOneSecond(CarbonImmutable::now(), $beatmapset->queued_at); 225 $this->assertEquals($beatmapset->approved_date, $beatmapset->queued_at); 226 } 227 228 public function testTimerIncreasesWhileDisqualified() 229 { 230 $weeksDisqualified = 2; 231 $disqualifiedDate = CarbonImmutable::now()->subWeeks($weeksDisqualified); 232 $qualifiedDate = $disqualifiedDate->subSeconds(static::DISQUALIFIED_INTERVAL)->startOfSecond(); 233 234 $this->travelTo($qualifiedDate); 235 236 $beatmapset = $this->beatmapsetFactory()->create(); 237 $nominators = $beatmapset->beatmapsetNominations()->get()->pluck('user'); 238 239 $this->travelTo($disqualifiedDate); 240 241 $discussion = $this->disqualifyOrResetNominations($beatmapset); 242 $beatmapset = $beatmapset->fresh(); 243 244 $this->travelBack(); 245 246 $this->resolveDiscussionAndNominate($discussion, $nominators); 247 $beatmapset = $beatmapset->fresh(); 248 249 $this->assertTrue($beatmapset->isQualified()); 250 $this->assertEquals($beatmapset->approved_date->toImmutable()->addDays($weeksDisqualified)->subSeconds(static::DISQUALIFIED_INTERVAL), $beatmapset->queued_at); 251 } 252 253 protected function setUp(): void 254 { 255 parent::setUp(); 256 257 $this->user = User::factory()->withGroup('bng', ['osu'])->create()->markSessionVerified(); 258 259 Genre::factory()->create(['genre_id' => Genre::UNSPECIFIED]); 260 Language::factory()->create(['language_id' => Language::UNSPECIFIED]); 261 262 Bus::fake([CheckBeatmapsetCovers::class]); 263 } 264 265 private function assertDiffUpToOneSecond(int $expected, int $actual) 266 { 267 $this->assertTrue(abs($actual - $expected) < 2); 268 } 269 270 private function beatmapsetFactory(): BeatmapsetFactory 271 { 272 return Beatmapset::factory() 273 ->owner() 274 ->qualified() 275 ->withBeatmaps('osu') 276 ->withNominations(); 277 } 278 279 private function disqualifyOrResetnominations(Beatmapset $beatmapset) 280 { 281 $discussion = BeatmapDiscussion::factory()->general()->problem()->create(['beatmapset_id' => $beatmapset, 'user_id' => $this->user]); 282 $beatmapset->disqualifyOrResetNominations($this->user, $discussion); 283 284 return $discussion; 285 } 286 287 private function resolveDiscussionAndNominate(BeatmapDiscussion $discussion, iterable $nominators) 288 { 289 (new Reply($this->user, $discussion, 'resolve', true))->handle(); 290 $beatmapset = $discussion->fresh()->beatmapset; 291 292 foreach ($nominators as $nominator) { 293 $beatmapset->nominate($nominator, ['osu']); 294 } 295 } 296}