From 55e68d034b70796d1d56fe6767be9436af648cb4 Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 14 Nov 2023 22:02:41 +0900 Subject: [PATCH 01/33] Move trait to helper (rename) --- .../Traits/UserAvatar.php => Libraries/User/AvatarHelper.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/{Models/Traits/UserAvatar.php => Libraries/User/AvatarHelper.php} (100%) diff --git a/app/Models/Traits/UserAvatar.php b/app/Libraries/User/AvatarHelper.php similarity index 100% rename from app/Models/Traits/UserAvatar.php rename to app/Libraries/User/AvatarHelper.php From 213b098851a7f5235f5d06b17ed72dbafe38a31a Mon Sep 17 00:00:00 2001 From: nanaya Date: Wed, 18 Oct 2023 20:37:25 +0900 Subject: [PATCH 02/33] Use separate helper to handle user avatar --- app/Http/Controllers/AccountController.php | 3 +- app/Libraries/User/AvatarHelper.php | 94 ++++++++++++---------- app/Models/User.php | 5 +- tests/Models/Chat/ChannelTest.php | 5 +- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index eb5b75ce9ee..1697f079446 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -7,6 +7,7 @@ use App\Exceptions\ImageProcessorException; use App\Exceptions\ModelNotSavedException; +use App\Libraries\User\AvatarHelper; use App\Libraries\User\CountryChange; use App\Libraries\User\CountryChangeTarget; use App\Libraries\UserVerification; @@ -73,7 +74,7 @@ public function avatar() $user = auth()->user(); try { - $user->setAvatar(Request::file('avatar_file')); + AvatarHelper::set($user, Request::file('avatar_file')); } catch (ImageProcessorException $e) { return error_popup($e->getMessage()); } diff --git a/app/Libraries/User/AvatarHelper.php b/app/Libraries/User/AvatarHelper.php index 252d6411665..6b4919dc507 100644 --- a/app/Libraries/User/AvatarHelper.php +++ b/app/Libraries/User/AvatarHelper.php @@ -3,65 +3,77 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. -namespace App\Models\Traits; +declare(strict_types=1); + +namespace App\Libraries\User; use App\Libraries\ImageProcessor; use App\Libraries\StorageUrl; -use ErrorException; +use App\Models\User; -trait UserAvatar +class AvatarHelper { - private static function avatarDisk(): string + public static function set(User $user, ?\SplFileInfo $src): bool { - return \Config::get('osu.avatar.storage'); - } + $id = $user->getKey(); + $storage = \Storage::disk(static::disk()); - public function setAvatar($file) - { - $storage = \Storage::disk(static::avatarDisk()); - if ($file === null) { - $storage->delete($this->user_id); + if ($src === null) { + $storage->delete($id); } else { - $filePath = $file->getRealPath(); - $processor = new ImageProcessor($filePath, [256, 256], 100000); + $srcPath = $src->getRealPath(); + $processor = new ImageProcessor($srcPath, [256, 256], 100000); $processor->process(); - $storage->put($this->user_id, file_get_contents($filePath), 'public'); - - $entry = $this->user_id.'_'.time().'.'.$processor->ext(); + $storage->putFileAs('/', $src, $id, 'public'); + $entry = $id.'_'.time().'.'.$processor->ext(); } - if (present(\Config::get('osu.avatar.cache_purge_prefix'))) { - try { - $ctx = [ - 'http' => [ - 'method' => \Config::get('osu.avatar.cache_purge_method') ?? 'GET', - 'header' => present(\Config::get('osu.avatar.cache_purge_authorization_key')) - ? 'Authorization: '.\Config::get('osu.avatar.cache_purge_authorization_key') - : null, - ], - ]; - $prefix = \Config::get('osu.avatar.cache_purge_prefix'); - $suffix = $ctx['http']['method'] === 'GET' ? '?'.time() : ''; // Bypass CloudFlare cache if using GET - $url = $prefix.$this->user_id.$suffix; - file_get_contents($url, false, stream_context_create($ctx)); - } catch (ErrorException $e) { - // ignores 404 errors, throws everything else - if (!ends_with($e->getMessage(), "404 Not Found\r\n")) { - throw $e; - } - } - } + static::purgeCache($id); - return $this->update(['user_avatar' => $entry ?? '']); + return $user->update(['user_avatar' => $entry ?? '']); } - protected function getUserAvatar() + public static function url(User $user): string { - $value = $this->getRawAttribute('user_avatar'); + $value = $user->getRawAttribute('user_avatar'); return present($value) - ? StorageUrl::make(static::avatarDisk(), strtr($value, '_', '?')) + ? StorageUrl::make(static::disk(), strtr($value, '_', '?')) : \Config::get('osu.avatar.default'); } + + private static function disk(): string + { + return \Config::get('osu.avatar.storage'); + } + + private static function purgeCache(int $id): void + { + $prefix = presence(\Config::get('osu.avatar.cache_purge_prefix')); + + if ($prefix === null) { + return; + } + + $method = \Config::get('osu.avatar.cache_purge_method') ?? 'GET'; + $auth = \Config::get('osu.avatar.cache_purge_authorization_key'); + $ctx = [ + 'http' => [ + 'method' => $method, + 'header' => present($auth) ? "Authorization: {$auth}" : null, + ], + ]; + $suffix = $method === 'GET' ? '?'.time() : ''; // Bypass CloudFlare cache if using GET + $url = "{$prefix}{$id}{$suffix}"; + + try { + file_get_contents($url, false, stream_context_create($ctx)); + } catch (\ErrorException $e) { + // ignores 404 errors, throws everything else + if (!ends_with($e->getMessage(), "404 Not Found\r\n")) { + throw $e; + } + } + } } diff --git a/app/Models/User.php b/app/Models/User.php index 013951fe5a7..04a34a07aa9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,6 +14,7 @@ use App\Libraries\Elasticsearch\Indexable; use App\Libraries\Session\Store as SessionStore; use App\Libraries\Transactions\AfterCommit; +use App\Libraries\User\AvatarHelper; use App\Libraries\User\DatadogLoginAttempt; use App\Libraries\User\ProfileBeatmapset; use App\Libraries\User\UsernamesForDbLookup; @@ -212,7 +213,7 @@ */ class User extends Model implements AfterCommit, AuthenticatableContract, HasLocalePreference, Indexable, Traits\ReportableInterface { - use Authenticatable, HasApiTokens, Memoizes, Traits\Es\UserSearch, Traits\Reportable, Traits\UserAvatar, Traits\UserScoreable, Traits\UserStore, Validatable; + use Authenticatable, HasApiTokens, Memoizes, Traits\Es\UserSearch, Traits\Reportable, Traits\UserScoreable, Traits\UserStore, Validatable; const PLAYSTYLES = [ 'mouse' => 1, @@ -837,7 +838,7 @@ public function getAttribute($key) 'displayed_last_visit' => $this->getDisplayedLastVisit(), 'osu_playstyle' => $this->getOsuPlaystyle(), 'playmode' => $this->getPlaymode(), - 'user_avatar' => $this->getUserAvatar(), + 'user_avatar' => AvatarHelper::url($this), 'user_colour' => $this->getUserColour(), 'user_rank' => $this->getUserRank(), 'user_website' => $this->getUserWebsite(), diff --git a/tests/Models/Chat/ChannelTest.php b/tests/Models/Chat/ChannelTest.php index 071111b51bf..419b0355b97 100644 --- a/tests/Models/Chat/ChannelTest.php +++ b/tests/Models/Chat/ChannelTest.php @@ -9,6 +9,7 @@ use App\Events\ChatChannelEvent; use App\Jobs\Notifications\ChannelAnnouncement; +use App\Libraries\User\AvatarHelper; use App\Models\Chat\Channel; use App\Models\User; use App\Models\UserRelation; @@ -254,8 +255,8 @@ public function testPmChannelIcon() $otherUser = User::factory()->create(); $testFile = new SplFileInfo(public_path('images/layout/avatar-guest.png')); - $user->setAvatar($testFile); - $otherUser->setAvatar($testFile); + AvatarHelper::set($user, $testFile); + AvatarHelper::set($otherUser, $testFile); $channel = Channel::factory()->type('pm', [$user, $otherUser])->create(); $this->assertSame($otherUser->user_avatar, $channel->displayIconFor($user)); From 7457c6559965812f4a8f721654e700632eca1243 Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 21 Nov 2023 15:12:51 +0900 Subject: [PATCH 03/33] Add verification link validation --- app/Http/Controllers/AccountController.php | 2 +- app/Libraries/UserVerificationLink.php | 40 ++++++++++++++++++++++ app/Libraries/UserVerificationState.php | 6 +++- app/helpers.php | 22 ++++++++---- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 app/Libraries/UserVerificationLink.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index eb5b75ce9ee..07406ac9546 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -308,7 +308,7 @@ public function verify() public function verifyLink() { - $state = UserVerificationState::fromVerifyLink(request('key')); + $state = UserVerificationState::fromVerifyLink(get_string(request('key')) ?? ''); if ($state === null) { UserVerification::logAttempt('link', 'fail', 'incorrect_key'); diff --git a/app/Libraries/UserVerificationLink.php b/app/Libraries/UserVerificationLink.php new file mode 100644 index 00000000000..a6598ba0866 --- /dev/null +++ b/app/Libraries/UserVerificationLink.php @@ -0,0 +1,40 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace App\Libraries; + +class UserVerificationLink +{ + private const KEY_SIZE = 32; + + public static function create(): string + { + $key = random_bytes(static::KEY_SIZE); + $hmac = static::hmac($key); + + return base64url_encode($key.$hmac); + } + + public static function isValid(string $link): bool + { + $linkBin = base64url_decode($link); + if ($linkBin === null) { + return false; + } + + $key = substr($linkBin, 0, static::KEY_SIZE); + $hmac = substr($linkBin, static::KEY_SIZE); + $expectedHmac = static::hmac($key); + + return hash_equals($expectedHmac, $hmac); + } + + private static function hmac(string $key): string + { + return hash_hmac('sha1', $key, \Crypt::getKey(), true); + } +} diff --git a/app/Libraries/UserVerificationState.php b/app/Libraries/UserVerificationState.php index 266ff6abe11..5ec90d94fed 100644 --- a/app/Libraries/UserVerificationState.php +++ b/app/Libraries/UserVerificationState.php @@ -23,6 +23,10 @@ public static function fromCurrentRequest() public static function fromVerifyLink($linkKey) { + if (!UserVerificationLink::isValid($linkKey)) { + return null; + } + $params = cache()->get("verification:{$linkKey}"); if ($params !== null) { @@ -76,7 +80,7 @@ public function issue() // 1 byte = 2^8 bits = 16^2 bits = 2 hex characters $key = bin2hex(random_bytes(config('osu.user.verification_key_length_hex') / 2)); - $linkKey = bin2hex(random_bytes(32)); + $linkKey = UserVerificationLink::create(); $expires = now()->addHours(5); $this->session->put('verification_key', $key); diff --git a/app/helpers.php b/app/helpers.php index 6e22360737c..4f20c03cdf3 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -60,6 +60,18 @@ function background_image($url, $proxy = true) return sprintf(' style="background-image:url(\'%s\');" ', e($url)); } +function base64url_decode(string $value): ?string +{ + return null_if_false(base64_decode(strtr($value, '-_', '+/'), true)); +} + +function base64url_encode(string $value): string +{ + // url safe base64 + // reference: https://datatracker.ietf.org/doc/html/rfc4648#section-5 + return rtrim(strtr(base64_encode($value), '+/', '-_'), '='); +} + function beatmap_timestamp_format($ms) { $s = $ms / 1000; @@ -310,13 +322,9 @@ function cursor_decode($cursorString): ?array function cursor_encode(?array $cursor): ?string { - if ($cursor === null) { - return null; - } - - // url safe base64 - // reference: https://datatracker.ietf.org/doc/html/rfc4648#section-5 - return rtrim(strtr(base64_encode(json_encode($cursor)), '+/', '-_'), '='); + return $cursor === null + ? null + : base64url_encode(json_encode($cursor)); } function cursor_for_response(?array $cursor): array From 738b90939ae13112b6d723e9ec84ce8a54c31f3a Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 21 Nov 2023 17:23:54 +0900 Subject: [PATCH 04/33] Add tests --- tests/Libraries/UserVerificationLinkTest.php | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/Libraries/UserVerificationLinkTest.php diff --git a/tests/Libraries/UserVerificationLinkTest.php b/tests/Libraries/UserVerificationLinkTest.php new file mode 100644 index 00000000000..319839ea417 --- /dev/null +++ b/tests/Libraries/UserVerificationLinkTest.php @@ -0,0 +1,36 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace Tests\Libraries; + +use App\Libraries\UserVerificationLink; +use Tests\TestCase; + +class UserVerificationLinkTest extends TestCase +{ + public function testIsValid(): void + { + $this->assertTrue(UserVerificationLink::isValid(UserVerificationLink::create())); + } + + /** + * @dataProvider dataProviderForTestIsValidInvalid + */ + public function testIsValidInvalid(string $value): void + { + $this->assertFalse(UserVerificationLink::isValid($value)); + } + + public function dataProviderForTestIsValidInvalid(): array + { + return [ + ['invalid'], + [''], + [base64url_encode('')], + ]; + } +} From 720bc038a4e959e8976a9cd0403ccf6ef67e8b0f Mon Sep 17 00:00:00 2001 From: nanaya Date: Wed, 22 Nov 2023 14:59:11 +0900 Subject: [PATCH 05/33] Use correct int size for multiplayer attempts column --- ...ultiplayer_scores_high_attempts_to_int.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 database/migrations/2023_11_22_055714_change_multiplayer_scores_high_attempts_to_int.php diff --git a/database/migrations/2023_11_22_055714_change_multiplayer_scores_high_attempts_to_int.php b/database/migrations/2023_11_22_055714_change_multiplayer_scores_high_attempts_to_int.php new file mode 100644 index 00000000000..fc174c84ae7 --- /dev/null +++ b/database/migrations/2023_11_22_055714_change_multiplayer_scores_high_attempts_to_int.php @@ -0,0 +1,27 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + public function up(): void + { + Schema::table('multiplayer_scores_high', function (Blueprint $table) { + $table->unsignedInteger('attempts')->default(0)->change(); + }); + } + + public function down(): void + { + Schema::table('multiplayer_scores_high', function (Blueprint $table) { + $table->unsignedTinyInteger('attempts')->default(0)->change(); + }); + } +}; From f5480c696f1856f0aa1e27ea496f9f2ac06091c2 Mon Sep 17 00:00:00 2001 From: bakaneko Date: Wed, 22 Nov 2023 18:46:05 +0900 Subject: [PATCH 06/33] fix chat input box going into wrong flow context on safari --- resources/css/bem/chat-conversation-panel.less | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/css/bem/chat-conversation-panel.less b/resources/css/bem/chat-conversation-panel.less index de9dabc370e..27d373d547f 100644 --- a/resources/css/bem/chat-conversation-panel.less +++ b/resources/css/bem/chat-conversation-panel.less @@ -5,9 +5,15 @@ display: flex; flex-direction: column; flex: 1; - overflow-y: auto; // without this, firefox scroll breaks position: relative; + @media @mobile { + overflow-y: hidden; // Needed to limit the height of the panel so chat-conversation is scrollable and + // chat-conversation-list doesn't become 0 height on mobile. + // height: 100% alone doesn't work. + // The panel itself should not be scrollable. + } + &__instructions { margin-top: 10px; } From 6d9df7bc3487ea148b069b3c005e41b6fc57f15a Mon Sep 17 00:00:00 2001 From: nanaya Date: Wed, 22 Nov 2023 19:08:21 +0900 Subject: [PATCH 07/33] Simplify cookie header removal --- app/Http/Middleware/StripCookies.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Http/Middleware/StripCookies.php b/app/Http/Middleware/StripCookies.php index 1899047b2af..05168a701ab 100644 --- a/app/Http/Middleware/StripCookies.php +++ b/app/Http/Middleware/StripCookies.php @@ -15,13 +15,7 @@ public function handle($request, Closure $next) if ($request->attributes->get('strip_cookies')) { // strip all cookies from response - foreach ($result->headers->getCookies() as $cookie) { - $result->headers->removeCookie( - $cookie->getName(), - $cookie->getPath(), - $cookie->getDomain() - ); - } + $result->headers->remove('set-cookie'); } return $result; From 233a12013374b7d5c7a7fc9fe1a511a7860eff84 Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 15:27:55 +0900 Subject: [PATCH 08/33] More generic class name --- ...icationLink.php => SignedRandomString.php} | 21 +++++++++---------- app/Libraries/UserVerificationState.php | 4 ++-- ...inkTest.php => SignedRandomStringTest.php} | 10 ++++----- 3 files changed, 17 insertions(+), 18 deletions(-) rename app/Libraries/{UserVerificationLink.php => SignedRandomString.php} (56%) rename tests/Libraries/{UserVerificationLinkTest.php => SignedRandomStringTest.php} (68%) diff --git a/app/Libraries/UserVerificationLink.php b/app/Libraries/SignedRandomString.php similarity index 56% rename from app/Libraries/UserVerificationLink.php rename to app/Libraries/SignedRandomString.php index a6598ba0866..ef4f339f3e1 100644 --- a/app/Libraries/UserVerificationLink.php +++ b/app/Libraries/SignedRandomString.php @@ -7,27 +7,26 @@ namespace App\Libraries; -class UserVerificationLink +class SignedRandomString { - private const KEY_SIZE = 32; - - public static function create(): string + public static function create(int $randomSize): string { - $key = random_bytes(static::KEY_SIZE); + $key = random_bytes($randomSize); $hmac = static::hmac($key); - return base64url_encode($key.$hmac); + return base64url_encode($hmac.$key); } - public static function isValid(string $link): bool + public static function isValid(string $input): bool { - $linkBin = base64url_decode($link); - if ($linkBin === null) { + $bin = base64url_decode($input); + if ($bin === null) { return false; } - $key = substr($linkBin, 0, static::KEY_SIZE); - $hmac = substr($linkBin, static::KEY_SIZE); + // hmac size for sha1 is 20 + $hmac = substr($bin, 0, 20); + $key = substr($bin, 20); $expectedHmac = static::hmac($key); return hash_equals($expectedHmac, $hmac); diff --git a/app/Libraries/UserVerificationState.php b/app/Libraries/UserVerificationState.php index 5ec90d94fed..61c336ee09a 100644 --- a/app/Libraries/UserVerificationState.php +++ b/app/Libraries/UserVerificationState.php @@ -23,7 +23,7 @@ public static function fromCurrentRequest() public static function fromVerifyLink($linkKey) { - if (!UserVerificationLink::isValid($linkKey)) { + if (!SignedRandomString::isValid($linkKey)) { return null; } @@ -80,7 +80,7 @@ public function issue() // 1 byte = 2^8 bits = 16^2 bits = 2 hex characters $key = bin2hex(random_bytes(config('osu.user.verification_key_length_hex') / 2)); - $linkKey = UserVerificationLink::create(); + $linkKey = SignedRandomString::create(32); $expires = now()->addHours(5); $this->session->put('verification_key', $key); diff --git a/tests/Libraries/UserVerificationLinkTest.php b/tests/Libraries/SignedRandomStringTest.php similarity index 68% rename from tests/Libraries/UserVerificationLinkTest.php rename to tests/Libraries/SignedRandomStringTest.php index 319839ea417..b917154a0f6 100644 --- a/tests/Libraries/UserVerificationLinkTest.php +++ b/tests/Libraries/SignedRandomStringTest.php @@ -7,14 +7,14 @@ namespace Tests\Libraries; -use App\Libraries\UserVerificationLink; +use App\Libraries\SignedRandomString; use Tests\TestCase; -class UserVerificationLinkTest extends TestCase +class SignedRandomStringTest extends TestCase { public function testIsValid(): void { - $this->assertTrue(UserVerificationLink::isValid(UserVerificationLink::create())); + $this->assertTrue(SignedRandomString::isValid(SignedRandomString::create(40))); } /** @@ -22,7 +22,7 @@ public function testIsValid(): void */ public function testIsValidInvalid(string $value): void { - $this->assertFalse(UserVerificationLink::isValid($value)); + $this->assertFalse(SignedRandomString::isValid($value)); } public function dataProviderForTestIsValidInvalid(): array @@ -30,7 +30,7 @@ public function dataProviderForTestIsValidInvalid(): array return [ ['invalid'], [''], - [base64url_encode('')], + [base64url_encode('test')], ]; } } From 83547432b71fd009f8d91d8010c737d33602ab9b Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 15:56:30 +0900 Subject: [PATCH 09/33] Support ruleset parameter for beatmap page --- app/Enums/Ruleset.php | 16 ++++++++++++++-- app/Http/Controllers/BeatmapsController.php | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/Enums/Ruleset.php b/app/Enums/Ruleset.php index 1fc36baeedc..890e4a8aeae 100644 --- a/app/Enums/Ruleset.php +++ b/app/Enums/Ruleset.php @@ -14,16 +14,28 @@ enum Ruleset: int case catch = 2; case mania = 3; - public static function fromName(string $ruleset): self + public static function tryFromName(?string $ruleset): ?self { + if ($ruleset === null) { + return null; + } + static $lookupMap; if ($lookupMap === null) { $lookupMap = []; foreach (self::cases() as $r) { $lookupMap[$r->name] = $r; } + $lookupMap['fruits'] = self::catch; } - return $lookupMap[$ruleset]; + return $lookupMap[$ruleset] ?? null; + } + + public function legacyName() + { + return $this === self::catch + ? 'fruits' + : $this->name; } } diff --git a/app/Http/Controllers/BeatmapsController.php b/app/Http/Controllers/BeatmapsController.php index c82345bb533..295217bbefc 100644 --- a/app/Http/Controllers/BeatmapsController.php +++ b/app/Http/Controllers/BeatmapsController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; +use App\Enums\Ruleset; use App\Exceptions\InvariantException; use App\Jobs\Notifications\BeatmapOwnerChange; use App\Libraries\BeatmapDifficultyAttributes; @@ -256,20 +257,24 @@ public function show($id) abort(404); } - if ($beatmap->mode === 'osu') { + $beatmapRuleset = $beatmap->mode; + if ($beatmapRuleset === 'osu') { $params = get_params(request()->all(), null, [ 'm:int', // legacy parameter - 'mode:string', + 'mode', // legacy parameter + 'ruleset', ], ['null_missing' => true]); - $mode = Beatmap::isModeValid($params['mode']) - ? $params['mode'] - : Beatmap::modeStr($params['m']); + $ruleset = ( + Ruleset::tryFromName($params['ruleset']) + ?? Ruleset::tryFromName($params['mode']) + ?? Ruleset::tryFrom($params['m']) + )?->legacyName(); } - $mode ??= $beatmap->mode; + $ruleset ??= $beatmapRuleset; - return ujs_redirect(route('beatmapsets.show', ['beatmapset' => $beatmapset->getKey()]).'#'.$mode.'/'.$beatmap->getKey()); + return ujs_redirect(route('beatmapsets.show', ['beatmapset' => $beatmapset->getKey()]).'#'.$ruleset.'/'.$beatmap->getKey()); } /** From c7fc70d5f6927da5318db13e8360930f8d7055a9 Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 16:06:19 +0900 Subject: [PATCH 10/33] Don't explode on invalid m parameter --- app/Enums/Ruleset.php | 2 ++ app/Http/Controllers/BeatmapsController.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Enums/Ruleset.php b/app/Enums/Ruleset.php index 890e4a8aeae..806f47d6668 100644 --- a/app/Enums/Ruleset.php +++ b/app/Enums/Ruleset.php @@ -14,6 +14,8 @@ enum Ruleset: int case catch = 2; case mania = 3; + public const invalid = -1; + public static function tryFromName(?string $ruleset): ?self { if ($ruleset === null) { diff --git a/app/Http/Controllers/BeatmapsController.php b/app/Http/Controllers/BeatmapsController.php index 295217bbefc..3021b81bd1b 100644 --- a/app/Http/Controllers/BeatmapsController.php +++ b/app/Http/Controllers/BeatmapsController.php @@ -268,7 +268,7 @@ public function show($id) $ruleset = ( Ruleset::tryFromName($params['ruleset']) ?? Ruleset::tryFromName($params['mode']) - ?? Ruleset::tryFrom($params['m']) + ?? Ruleset::tryFrom($params['m'] ?? Ruleset::invalid) )?->legacyName(); } From 2c48fd0d12608c611724d501f536c96b8eb73f70 Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 16:11:04 +0900 Subject: [PATCH 11/33] Valid const naming --- app/Enums/Ruleset.php | 2 +- app/Http/Controllers/BeatmapsController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Enums/Ruleset.php b/app/Enums/Ruleset.php index 806f47d6668..2a379d95b4c 100644 --- a/app/Enums/Ruleset.php +++ b/app/Enums/Ruleset.php @@ -14,7 +14,7 @@ enum Ruleset: int case catch = 2; case mania = 3; - public const invalid = -1; + public const NULL = -1; public static function tryFromName(?string $ruleset): ?self { diff --git a/app/Http/Controllers/BeatmapsController.php b/app/Http/Controllers/BeatmapsController.php index 3021b81bd1b..97158c45737 100644 --- a/app/Http/Controllers/BeatmapsController.php +++ b/app/Http/Controllers/BeatmapsController.php @@ -268,7 +268,7 @@ public function show($id) $ruleset = ( Ruleset::tryFromName($params['ruleset']) ?? Ruleset::tryFromName($params['mode']) - ?? Ruleset::tryFrom($params['m'] ?? Ruleset::invalid) + ?? Ruleset::tryFrom($params['m'] ?? Ruleset::NULL) )?->legacyName(); } From 392302ce852055fbad80ba2cb4dbd43f995f7d4c Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 16:12:14 +0900 Subject: [PATCH 12/33] Document the weird const --- app/Enums/Ruleset.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Enums/Ruleset.php b/app/Enums/Ruleset.php index 2a379d95b4c..3ff92da8ee4 100644 --- a/app/Enums/Ruleset.php +++ b/app/Enums/Ruleset.php @@ -14,6 +14,7 @@ enum Ruleset: int case catch = 2; case mania = 3; + // for usage with tryFrom when the parameter may be null. public const NULL = -1; public static function tryFromName(?string $ruleset): ?self From c579f2c14e2b023e11cd2d18258d14f85459c211 Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 16:31:59 +0900 Subject: [PATCH 13/33] Simpler api request check --- app/Http/Middleware/DatadogMetrics.php | 2 +- app/helpers.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Middleware/DatadogMetrics.php b/app/Http/Middleware/DatadogMetrics.php index 89b27a7be94..68a8f1f710f 100644 --- a/app/Http/Middleware/DatadogMetrics.php +++ b/app/Http/Middleware/DatadogMetrics.php @@ -32,7 +32,7 @@ protected static function logDuration(Request $request, Response $response, $sta $duration = microtime(true) - $startTime; $tags = [ 'action' => 'error_page', - 'api' => $request->is('api/*') ? 'true' : 'false', + 'api' => is_api_request() ? 'true' : 'false', 'controller' => 'error', 'namespace' => 'error', 'pod_name' => $hostname, diff --git a/app/helpers.php b/app/helpers.php index 6e22360737c..f039f500d20 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -791,14 +791,14 @@ function forum_user_link(int $id, string $username, string|null $colour, int|nul return "{$icon} {$link}"; } -function is_api_request() +function is_api_request(): bool { - return request()->is('api/*'); + return str_starts_with(rawurldecode(Request::getPathInfo()), '/api/'); } -function is_json_request() +function is_json_request(): bool { - return is_api_request() || request()->expectsJson(); + return is_api_request() || Request::expectsJson(); } function is_valid_email_format(?string $email): bool From e52714a2ca7ea6e2865f3d7182e3cb09937a749d Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 17:09:14 +0900 Subject: [PATCH 14/33] Also update the facade instance --- tests/Middleware/RequireScopesTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Middleware/RequireScopesTest.php b/tests/Middleware/RequireScopesTest.php index a39e14ffc42..2cd460ac9d1 100644 --- a/tests/Middleware/RequireScopesTest.php +++ b/tests/Middleware/RequireScopesTest.php @@ -155,6 +155,7 @@ protected function setRequest(?array $scopes = null, $request = null) // so request() works $this->app->instance('request', $this->request); + \Request::swap($this->request); // set a fake route resolver $this->request->setRouteResolver(function () use ($scopes) { From 4dde62e1cdfc9174ef555d3f1c69db6248c3502a Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 18:07:59 +0900 Subject: [PATCH 15/33] Update user last visit on game client access This assumes it's from client iff the token has `*` scope. --- app/Http/Kernel.php | 1 + app/Http/Middleware/UpdateUserLastvisit.php | 32 ++++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a45cf05a3df..c6062330a92 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -23,6 +23,7 @@ class Kernel extends HttpKernel Middleware\AuthApi::class, Middleware\SetLocaleApi::class, Middleware\CheckUserBanStatus::class, + Middleware\UpdateUserLastvisit::class, ], 'web' => [ Middleware\StripCookies::class, diff --git a/app/Http/Middleware/UpdateUserLastvisit.php b/app/Http/Middleware/UpdateUserLastvisit.php index 063605a2cc2..b0065ea54e6 100644 --- a/app/Http/Middleware/UpdateUserLastvisit.php +++ b/app/Http/Middleware/UpdateUserLastvisit.php @@ -24,24 +24,30 @@ public function handle($request, Closure $next) $user = $this->auth->user(); if ($user !== null) { - $isInactive = $user->isInactive(); + $token = $user->token(); + $shouldUpdate = $token === null || in_array('*', $token->scopes, true); - if ($isInactive) { - $isVerified = $user->isSessionVerified(); - } + if ($shouldUpdate) { + $isInactive = $user->isInactive(); + if ($isInactive) { + $isVerified = $user->isSessionVerified(); + } - if (!$isInactive || $isVerified) { - $recordedLastVisit = $user->getRawAttribute('user_lastvisit'); - $currentLastVisit = time(); + if (!$isInactive || $isVerified) { + $recordedLastVisit = $user->getRawAttribute('user_lastvisit'); + $currentLastVisit = time(); - if ($currentLastVisit - $recordedLastVisit > 300) { - $user->update([ - 'user_lastvisit' => $currentLastVisit, - ], ['skipValidations' => true]); + if ($currentLastVisit - $recordedLastVisit > 300) { + $user->update([ + 'user_lastvisit' => $currentLastVisit, + ], ['skipValidations' => true]); + } } - } - $this->recordSession($request); + if ($token === null) { + $this->recordSession($request); + } + } } return $next($request); From a844b3663376b9e057ad10c56914bceda65fff7f Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 18:23:57 +0900 Subject: [PATCH 16/33] The facade swap already handles instance replacement --- tests/Middleware/RequireScopesTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Middleware/RequireScopesTest.php b/tests/Middleware/RequireScopesTest.php index 2cd460ac9d1..053059a1f57 100644 --- a/tests/Middleware/RequireScopesTest.php +++ b/tests/Middleware/RequireScopesTest.php @@ -154,7 +154,6 @@ protected function setRequest(?array $scopes = null, $request = null) }; // so request() works - $this->app->instance('request', $this->request); \Request::swap($this->request); // set a fake route resolver From 07a15a7cb1949ed100a47544972117c9452bba1a Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 24 Nov 2023 20:50:27 +0900 Subject: [PATCH 17/33] Change to check password client type instead --- app/Http/Middleware/AuthApi.php | 1 + app/Http/Middleware/UpdateUserLastvisit.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Middleware/AuthApi.php b/app/Http/Middleware/AuthApi.php index 72c46653816..592c1ef5a17 100644 --- a/app/Http/Middleware/AuthApi.php +++ b/app/Http/Middleware/AuthApi.php @@ -79,6 +79,7 @@ private function validTokenFromRequest($psr) throw new AuthenticationException('invalid token'); } + $token->setRelation('client', $client); $token->validate(); $user = $token->getResourceOwner(); diff --git a/app/Http/Middleware/UpdateUserLastvisit.php b/app/Http/Middleware/UpdateUserLastvisit.php index b0065ea54e6..a817773224a 100644 --- a/app/Http/Middleware/UpdateUserLastvisit.php +++ b/app/Http/Middleware/UpdateUserLastvisit.php @@ -25,7 +25,7 @@ public function handle($request, Closure $next) if ($user !== null) { $token = $user->token(); - $shouldUpdate = $token === null || in_array('*', $token->scopes, true); + $shouldUpdate = $token === null || $token->client->password_client; if ($shouldUpdate) { $isInactive = $user->isInactive(); From c182cdc8da956a7bfa7ed23d35b8707af803e635 Mon Sep 17 00:00:00 2001 From: bakaneko Date: Mon, 27 Nov 2023 16:44:37 +0900 Subject: [PATCH 18/33] don't update autosize on component update --- resources/js/components/textarea-autosize.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/js/components/textarea-autosize.tsx b/resources/js/components/textarea-autosize.tsx index b47d3da401a..4b120e5d856 100644 --- a/resources/js/components/textarea-autosize.tsx +++ b/resources/js/components/textarea-autosize.tsx @@ -61,8 +61,6 @@ export default class TextareaAutosize extends React.Component { if (this.ref.current.style.overflowX !== 'hidden') { this.ref.current.style.overflowX = 'hidden'; } - - autosize.update(this.ref.current); } componentWillUnmount() { From 9f62566b0722753d5783ee7f64c31cea9cb9e53c Mon Sep 17 00:00:00 2001 From: nanaya Date: Mon, 27 Nov 2023 21:11:44 +0900 Subject: [PATCH 19/33] Simplify laravel provider list --- config/app.php | 70 ++++++++++---------------------------------------- 1 file changed, 14 insertions(+), 56 deletions(-) diff --git a/config/app.php b/config/app.php index 1c483773537..d39297d1714 100644 --- a/config/app.php +++ b/config/app.php @@ -1,6 +1,7 @@ [ - - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - // Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - // Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - // We're using our own SessionServiceProvider so we can override the session id naming (for redis key namespacing) - App\Providers\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - - /* - * Package Service Providers... - */ - GrahamCampbell\GitHub\GitHubServiceProvider::class, - Mariuzzo\LaravelJsLocalization\LaravelJsLocalizationServiceProvider::class, - Laravel\Tinker\TinkerServiceProvider::class, - - /* - * Application Service Providers... - */ + 'providers' => ServiceProvider::defaultProviders()->except([ + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + ])->merge([ App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, App\Providers\EventServiceProvider::class, + // Override default migrate:fresh + App\Providers\MigrationServiceProvider::class, App\Providers\RouteServiceProvider::class, - - /* - * After DB transaction commit support - */ + // Override the session id naming (for redis key namespacing) + App\Providers\SessionServiceProvider::class, + // After DB transaction commit support App\Providers\TransactionStateServiceProvider::class, - /* - * OAuth2 Setup - */ - - App\Providers\AuthServiceProvider::class, - Laravel\Passport\PassportServiceProvider::class, - - /* Datadog Metrics */ - ChaseConey\LaravelDatadogHelper\LaravelDatadogHelperServiceProvider::class, - - /* Override default migrate:fresh */ - App\Providers\MigrationServiceProvider::class, - ], + Mariuzzo\LaravelJsLocalization\LaravelJsLocalizationServiceProvider::class, + ])->toArray(), /* |-------------------------------------------------------------------------- From a1cb854ff80c2ead404928628b972eb6d446f174 Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 28 Nov 2023 16:55:27 +0900 Subject: [PATCH 20/33] Use local cache for smilies data --- app/Libraries/BBCodeForDB.php | 3 +-- app/Libraries/Smilies.php | 26 ++++++++++++++++++++++++++ app/Models/Smiley.php | 10 ---------- app/Providers/AppServiceProvider.php | 2 ++ 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 app/Libraries/Smilies.php diff --git a/app/Libraries/BBCodeForDB.php b/app/Libraries/BBCodeForDB.php index 8d3fb94e44d..bed7da47b96 100644 --- a/app/Libraries/BBCodeForDB.php +++ b/app/Libraries/BBCodeForDB.php @@ -5,7 +5,6 @@ namespace App\Libraries; -use App\Models\Smiley; use App\Models\User; class BBCodeForDB @@ -329,7 +328,7 @@ public function parseSize($text) public function parseSmiley($text) { - $smilies = Smiley::getAll(); + $smilies = app('smilies')->all(); $match = []; $replace = []; diff --git a/app/Libraries/Smilies.php b/app/Libraries/Smilies.php new file mode 100644 index 00000000000..34770f2cc4e --- /dev/null +++ b/app/Libraries/Smilies.php @@ -0,0 +1,26 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace App\Libraries; + +use App\Models\Smiley; +use App\Traits\Memoizes; + +class Smilies +{ + use Memoizes; + + public function all(): array + { + return $this->memoize(__FUNCTION__, fn () => $this->fetch()); + } + + protected function fetch(): array + { + return Smiley::orderBy(\DB::raw('LENGTH(code)'), 'desc')->get()->toArray(); + } +} diff --git a/app/Models/Smiley.php b/app/Models/Smiley.php index e5d54ff431d..ae298595245 100644 --- a/app/Models/Smiley.php +++ b/app/Models/Smiley.php @@ -5,9 +5,6 @@ namespace App\Models; -use Cache; -use DB; - /** * @property string $code * @property int $display_on_posting @@ -21,11 +18,4 @@ class Smiley extends Model { protected $table = 'phpbb_smilies'; - - public static function getAll() - { - return Cache::rememberForever('smilies', function () { - return self::orderBy(DB::raw('LENGTH(code)'), 'desc')->get()->toArray(); - }); - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 52ec5525f68..8dc6a20424d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -22,6 +22,7 @@ use App\Libraries\OsuMessageSelector; use App\Libraries\RateLimiter; use App\Libraries\RouteSection; +use App\Libraries\Smilies; use App\Libraries\User\ScorePins; use Datadog; use Illuminate\Database\Eloquent\Relations\Relation; @@ -43,6 +44,7 @@ class AppServiceProvider extends ServiceProvider 'groups' => Groups::class, 'layout-cache' => LayoutCache::class, 'medals' => Medals::class, + 'smilies' => Smilies::class, ]; const SINGLETONS = [ From 8275b4e4806d118999175e3625349c0c0412c87b Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 28 Nov 2023 17:04:31 +0900 Subject: [PATCH 21/33] Can just be private --- app/Libraries/Smilies.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Libraries/Smilies.php b/app/Libraries/Smilies.php index 34770f2cc4e..5c8f37eaa1b 100644 --- a/app/Libraries/Smilies.php +++ b/app/Libraries/Smilies.php @@ -19,7 +19,7 @@ public function all(): array return $this->memoize(__FUNCTION__, fn () => $this->fetch()); } - protected function fetch(): array + private function fetch(): array { return Smiley::orderBy(\DB::raw('LENGTH(code)'), 'desc')->get()->toArray(); } From 35acaffa227146719abfbc0160a7af2ef3007c95 Mon Sep 17 00:00:00 2001 From: nanaya Date: Tue, 28 Nov 2023 18:00:01 +0900 Subject: [PATCH 22/33] Cache smiley regexps --- app/Libraries/BBCodeForDB.php | 15 +++------------ app/Libraries/Smilies.php | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/Libraries/BBCodeForDB.php b/app/Libraries/BBCodeForDB.php index bed7da47b96..88d7a8445ed 100644 --- a/app/Libraries/BBCodeForDB.php +++ b/app/Libraries/BBCodeForDB.php @@ -324,22 +324,13 @@ public function parseSize($text) ); } - // copied from www/forum/includes/message_parser.php#L1196 - public function parseSmiley($text) { - $smilies = app('smilies')->all(); - - $match = []; - $replace = []; + $replacer = app('smilies')->replacer(); - foreach ($smilies as $smiley) { - $match[] = '(?<=^|[\n .])'.preg_quote($smiley['code'], '#').'(?![^<>]*>)'; - $replace[] = ''.$smiley['code'].''; - } - if (count($match)) { + if (count($replacer['patterns']) > 0) { // Make sure the delimiter # is added in front and at the end of every element within $match - $text = trim(preg_replace(explode(chr(0), '#'.implode('#'.chr(0).'#', $match).'#'), $replace, $text)); + $text = trim(preg_replace($replacer['patterns'], $replacer['replacements'], $text)); } return $text; diff --git a/app/Libraries/Smilies.php b/app/Libraries/Smilies.php index 5c8f37eaa1b..136381ada4d 100644 --- a/app/Libraries/Smilies.php +++ b/app/Libraries/Smilies.php @@ -19,6 +19,23 @@ public function all(): array return $this->memoize(__FUNCTION__, fn () => $this->fetch()); } + public function replacer(): array + { + return $this->memoize(__FUNCTION__, function () { + $smilies = $this->all(); + + $patterns = []; + $replacements = []; + + foreach ($smilies as $smiley) { + $patterns[] = '#(?<=^|[\n .])'.preg_quote($smiley['code'], '#').'(?![^<>]*>)#'; + $replacements[] = ''.$smiley['code'].''; + } + + return compact('patterns', 'replacements'); + }); + } + private function fetch(): array { return Smiley::orderBy(\DB::raw('LENGTH(code)'), 'desc')->get()->toArray(); From 33c2c2fefdc5dd6249a257f4a009a028521b13aa Mon Sep 17 00:00:00 2001 From: bakaneko Date: Tue, 28 Nov 2023 19:19:39 +0900 Subject: [PATCH 23/33] Test correct set of OAuth routes registered --- app/Providers/AuthServiceProvider.php | 3 +- tests/Providers/AuthServiceProviderTest.php | 58 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/Providers/AuthServiceProviderTest.php diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index bcb8f9ea713..719bcb38c75 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,6 +12,7 @@ use Carbon\Carbon; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; +use Laravel\Passport\Http\Controllers\AccessTokenController; use Laravel\Passport\Http\Controllers\ApproveAuthorizationController; use Laravel\Passport\Http\Controllers\DenyAuthorizationController; use Laravel\Passport\Passport; @@ -52,7 +53,7 @@ public function boot() // RouteServiceProvider current runs before our provider, so Passport's default routes will override // those set in routes/web.php. Route::group(['prefix' => 'oauth', 'as' => 'oauth.'], function () { - Route::post('token', '\Laravel\Passport\Http\Controllers\AccessTokenController@issueToken')->middleware('throttle')->name('passport.token'); + Route::post('token', AccessTokenController::class.'@issueToken')->middleware('throttle')->name('passport.token'); Route::get('authorize', AuthorizationController::class.'@authorize') ->middleware(['web', 'verify-user']) ->name('authorizations.authorize'); diff --git a/tests/Providers/AuthServiceProviderTest.php b/tests/Providers/AuthServiceProviderTest.php new file mode 100644 index 00000000000..1528663567f --- /dev/null +++ b/tests/Providers/AuthServiceProviderTest.php @@ -0,0 +1,58 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace Tests\Providers; + +use App\Http\Controllers\OAuth\AuthorizedClientsController; +use App\Http\Controllers\OAuth\ClientsController; +use App\Http\Controllers\Passport\AuthorizationController; +use Laravel\Passport\Http\Controllers\AccessTokenController; +use Laravel\Passport\Http\Controllers\ApproveAuthorizationController; +use Laravel\Passport\Http\Controllers\DenyAuthorizationController; +use Request; +use Route; +use Tests\TestCase; + +class AuthServiceProviderTest extends TestCase +{ + /** + * @dataProvider oauthRoutesRegisteredDataProvider + */ + public function testOAuthRoutesRegistered($url, $method, $uses) + { + $route = Route::getRoutes()->match(Request::create($url, $method)); + $this->assertSame($uses, $route->action['uses']); + } + + public function testPassportDefaultRoutesNotRegistered() + { + $routeNames = array_keys(Route::getRoutes()->getRoutesByName()); + + foreach ($routeNames as $routeName) { + $this->assertStringStartsNotWith('passport.', $routeName); + } + } + + public function oauthRoutesRegisteredDataProvider() + { + return [ + ['oauth/authorize', 'GET', AuthorizationController::class.'@authorize'], + ['oauth/authorize', 'POST', ApproveAuthorizationController::class.'@approve'], + ['oauth/authorize', 'DELETE', DenyAuthorizationController::class.'@deny'], + + ['oauth/authorized-clients/1', 'DELETE', AuthorizedClientsController::class.'@destroy'], + + ['oauth/clients', 'GET', ClientsController::class.'@index'], + ['oauth/clients', 'POST', ClientsController::class.'@store'], + ['oauth/clients/1', 'PUT', ClientsController::class.'@update'], + ['oauth/clients/1', 'DELETE', ClientsController::class.'@destroy'], + ['oauth/clients/1/reset-secret', 'POST', ClientsController::class.'@resetSecret'], + + ['oauth/token', 'POST', AccessTokenController::class.'@issueToken'], + ]; + } +} From a898df0d5264bf899acd68fb9361b6ac95954a8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:21:58 +0000 Subject: [PATCH 24/33] Bump phpseclib/phpseclib from 3.0.20 to 3.0.34 Bumps [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) from 3.0.20 to 3.0.34. - [Release notes](https://github.com/phpseclib/phpseclib/releases) - [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md) - [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.20...3.0.34) --- updated-dependencies: - dependency-name: phpseclib/phpseclib dependency-type: indirect ... Signed-off-by: dependabot[bot] --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 91ddc6d9a0e..e01bc972f15 100644 --- a/composer.lock +++ b/composer.lock @@ -6434,16 +6434,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.20", + "version": "3.0.34", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67" + "reference": "56c79f16a6ae17e42089c06a2144467acc35348a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67", - "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a", + "reference": "56c79f16a6ae17e42089c06a2144467acc35348a", "shasum": "" }, "require": { @@ -6524,7 +6524,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.20" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34" }, "funding": [ { @@ -6540,7 +6540,7 @@ "type": "tidelift" } ], - "time": "2023-06-13T06:30:34+00:00" + "time": "2023-11-27T11:13:31+00:00" }, { "name": "psr/cache", From eabfa609762b1a8d130fd97b45780ee9f0254909 Mon Sep 17 00:00:00 2001 From: nanaya Date: Wed, 29 Nov 2023 15:26:01 +0900 Subject: [PATCH 25/33] Enforce native redis and ds extensions --- composer.json | 3 ++- composer.lock | 62 +++++---------------------------------------------- 2 files changed, 7 insertions(+), 58 deletions(-) diff --git a/composer.json b/composer.json index fdad702a921..a77c864bf5a 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,8 @@ } ], "require": { + "ext-ds": "*", + "ext-redis": "*", "anhskohbo/no-captcha": "^3.2", "chaseconey/laravel-datadog-helper": ">=1.2.0", "egulias/email-validator": "*", @@ -40,7 +42,6 @@ "maennchen/zipstream-php": "^2.1", "mariuzzo/laravel-js-localization": "*", "paypal/paypal-checkout-sdk": "*", - "php-ds/php-ds": "^1.3", "sentry/sentry-laravel": "*", "symfony/yaml": "*", "tightenco/ziggy": ">=0.8.1", diff --git a/composer.lock b/composer.lock index e01bc972f15..45ae87b2800 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cc91b3026bfa983837950f0e96fb7c8a", + "content-hash": "c748b63fdc90f32f3850b615cb80eb80", "packages": [ { "name": "anhskohbo/no-captcha", @@ -5807,61 +5807,6 @@ }, "time": "2021-09-14T21:35:26+00:00" }, - { - "name": "php-ds/php-ds", - "version": "v1.4.1", - "source": { - "type": "git", - "url": "https://github.com/php-ds/polyfill.git", - "reference": "43d2df301a9e2017f67b8c11d94a5222f9c00fd1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-ds/polyfill/zipball/43d2df301a9e2017f67b8c11d94a5222f9c00fd1", - "reference": "43d2df301a9e2017f67b8c11d94a5222f9c00fd1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.0" - }, - "provide": { - "ext-ds": "1.3.0" - }, - "require-dev": { - "php-ds/tests": "^1.3" - }, - "suggest": { - "ext-ds": "to improve performance and reduce memory usage" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rudi Theunissen", - "email": "rudolf.theunissen@gmail.com" - } - ], - "keywords": [ - "data structures", - "ds", - "php", - "polyfill" - ], - "support": { - "issues": "https://github.com/php-ds/polyfill/issues", - "source": "https://github.com/php-ds/polyfill/tree/v1.4.1" - }, - "time": "2022-03-09T20:39:30+00:00" - }, { "name": "php-http/cache-plugin", "version": "1.8.0", @@ -13882,7 +13827,10 @@ }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "ext-ds": "*", + "ext-redis": "*" + }, "platform-dev": [], "platform-overrides": { "php": "8.2.0" From d98f81eb31f1302c55686991c73e8e43ab907cfb Mon Sep 17 00:00:00 2001 From: nanaya Date: Wed, 29 Nov 2023 16:28:08 +0900 Subject: [PATCH 26/33] Change data provider to static method Non-static is deprecated in phpunit 10. Also: - updated phpunit dusk schema - skip creating app in provider if possible - move base64url to its own class (helper isn't available) - use Arr::random instead of array_rand_val - use config in a callback - read config file directly instead of relying on config function - change some test class helpers to static method --- app/Libraries/Base64Url.php | 23 +++++++++++++++ app/Libraries/SignedRandomString.php | 4 +-- app/helpers.php | 17 ++--------- phpunit.dusk.xml | 28 +++++++++---------- .../BeatmapsetDisqualifyNotificationsTest.php | 2 +- tests/BroadcastNotificationTest.php | 11 +++----- tests/Browser/SanityTest.php | 4 +-- tests/Commands/EsIndexScoresQueueTest.php | 4 +-- tests/Commands/ModdingRankCommandTest.php | 4 +-- tests/Controllers/AccountControllerTest.php | 2 +- .../BeatmapDiscussionsControllerTest.php | 8 +++--- .../BeatmapsControllerSoloScoresTest.php | 2 +- tests/Controllers/BeatmapsControllerTest.php | 4 +-- .../Controllers/BeatmapsetsControllerTest.php | 8 +++--- .../Chat/ChannelsControllerTest.php | 2 +- tests/Controllers/Chat/ChatControllerTest.php | 6 ++-- tests/Controllers/CommentsControllerTest.php | 4 +-- .../InterOp/UserGroupsControllerTest.php | 2 +- .../LegacyApiKeyControllerTest.php | 2 +- .../Rooms/Playlist/ScoresControllerTest.php | 4 +-- .../Multiplayer/RoomsControllerTest.php | 13 +++++---- .../OAuth/ClientsControllerTest.php | 2 +- .../Controllers/ScoreTokensControllerTest.php | 4 +-- tests/Controllers/UsersControllerTest.php | 2 +- tests/CreatesApplication.php | 13 ++++++--- tests/HelpersTest.php | 4 +-- tests/Libraries/BBCodeForDBTest.php | 4 +-- tests/Libraries/BBCodeFromDBTest.php | 8 +++--- .../BeatmapsetDiscussion/DiscussionTest.php | 26 ++++++++--------- .../BeatmapsetDiscussion/ReplyTest.php | 12 ++++---- .../BeatmapsetDiscussion/ReviewTest.php | 2 +- tests/Libraries/ChatTest.php | 10 +++---- .../SupporterTagFulfillmentTest.php | 2 +- tests/Libraries/Ip2AsnTest.php | 2 +- tests/Libraries/Markdown/ChatMarkdownTest.php | 4 +-- tests/Libraries/Markdown/ProcessorTest.php | 8 +++--- tests/Libraries/ModsTest.php | 4 +-- .../Search/BeatmapsetQueryParserTest.php | 2 +- .../BeatmapsetSearchRequestParamsTest.php | 4 +-- tests/Libraries/SignedRandomStringTest.php | 5 ++-- tests/Libraries/UsernameValidationTest.php | 6 ++-- tests/LocaleTest.php | 9 +++--- tests/Middleware/RequireScopesTest.php | 4 +-- tests/Middleware/RouteScopesTest.php | 17 ++++++----- tests/Middleware/ThrottleRequestsTest.php | 2 +- tests/Models/BeatmapDiscussionTest.php | 2 +- .../Models/BeatmapPackUserCompletionTest.php | 2 +- tests/Models/BeatmapsetTest.php | 6 ++-- tests/Models/ChangelogEntryTest.php | 2 +- tests/Models/Chat/ChannelTest.php | 8 +++--- tests/Models/CommentTest.php | 4 +-- tests/Models/ContestTest.php | 4 +-- .../Models/ModelCompositePrimaryKeysTest.php | 2 +- tests/Models/ModelTest.php | 2 +- tests/Models/Multiplayer/RoomTest.php | 3 +- tests/Models/OAuth/GroupPermissionTest.php | 2 +- tests/Models/OAuth/TokenTest.php | 12 ++++---- tests/Models/Score/ModelTest.php | 2 +- tests/Models/Solo/ScoreEsIndexTest.php | 2 +- tests/Models/Store/OrderItemTest.php | 2 +- tests/Models/UserNotificationTest.php | 2 +- tests/Models/UserReportTest.php | 2 +- tests/Models/UserStatistics/ModelTest.php | 4 +-- tests/Models/UserTest.php | 4 +-- tests/OAuthAuthCodeRequestTest.php | 2 +- tests/OAuthClientCredentialsRequestTest.php | 4 +-- tests/Providers/AuthServiceProviderTest.php | 2 +- tests/TestCase.php | 19 +++++++------ .../BeatmapDiscussionPostTransformerTest.php | 2 +- .../BeatmapDiscussionTransformerTest.php | 2 +- tests/Transformers/BeatmapTransformerTest.php | 2 +- .../BeatmapsetCompactTransformerTest.php | 2 +- .../BeatmapsetDescriptionTransformerTest.php | 2 +- .../BeatmapsetEventTransformerTest.php | 2 +- .../BeatmapsetTransformerTest.php | 2 +- tests/Transformers/CommentTransformerTest.php | 2 +- .../Transformers/DeclaredPermissionsTest.php | 4 +-- .../OAuth/ClientTransformerTest.php | 2 +- .../PollOptionTransformerTest.php | 2 +- .../UserCompactTransformerTest.php | 4 +-- tests/ZalgoTest.php | 2 +- 81 files changed, 225 insertions(+), 209 deletions(-) create mode 100644 app/Libraries/Base64Url.php diff --git a/app/Libraries/Base64Url.php b/app/Libraries/Base64Url.php new file mode 100644 index 00000000000..9e2a5252b42 --- /dev/null +++ b/app/Libraries/Base64Url.php @@ -0,0 +1,23 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace App\Libraries; + +class Base64Url +{ + public static function decode(string $value): ?string + { + return null_if_false(base64_decode(strtr($value, '-_', '+/'), true)); + } + + public static function encode(string $value): string + { + // url safe base64 + // reference: https://datatracker.ietf.org/doc/html/rfc4648#section-5 + return rtrim(strtr(base64_encode($value), '+/', '-_'), '='); + } +} diff --git a/app/Libraries/SignedRandomString.php b/app/Libraries/SignedRandomString.php index ef4f339f3e1..7e5cc35002f 100644 --- a/app/Libraries/SignedRandomString.php +++ b/app/Libraries/SignedRandomString.php @@ -14,12 +14,12 @@ public static function create(int $randomSize): string $key = random_bytes($randomSize); $hmac = static::hmac($key); - return base64url_encode($hmac.$key); + return Base64Url::encode($hmac.$key); } public static function isValid(string $input): bool { - $bin = base64url_decode($input); + $bin = Base64Url::decode($input); if ($bin === null) { return false; } diff --git a/app/helpers.php b/app/helpers.php index 8ff68a0b4c1..dbfe75eacf5 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -3,6 +3,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +use App\Libraries\Base64Url; use App\Libraries\LocaleMeta; use App\Models\LoginAttempt; use Egulias\EmailValidator\EmailValidator; @@ -60,18 +61,6 @@ function background_image($url, $proxy = true) return sprintf(' style="background-image:url(\'%s\');" ', e($url)); } -function base64url_decode(string $value): ?string -{ - return null_if_false(base64_decode(strtr($value, '-_', '+/'), true)); -} - -function base64url_encode(string $value): string -{ - // url safe base64 - // reference: https://datatracker.ietf.org/doc/html/rfc4648#section-5 - return rtrim(strtr(base64_encode($value), '+/', '-_'), '='); -} - function beatmap_timestamp_format($ms) { $s = $ms / 1000; @@ -310,7 +299,7 @@ function current_locale_meta(): LocaleMeta function cursor_decode($cursorString): ?array { if (is_string($cursorString) && present($cursorString)) { - $cursor = json_decode(base64_decode(strtr($cursorString, '-_', '+/'), true), true); + $cursor = json_decode(Base64Url::decode($cursorString) ?? '', true); if (is_array($cursor)) { return $cursor; @@ -324,7 +313,7 @@ function cursor_encode(?array $cursor): ?string { return $cursor === null ? null - : base64url_encode(json_encode($cursor)); + : Base64Url::encode(json_encode($cursor)); } function cursor_for_response(?array $cursor): array diff --git a/phpunit.dusk.xml b/phpunit.dusk.xml index 0fe4666ae8f..ef830df7a43 100644 --- a/phpunit.dusk.xml +++ b/phpunit.dusk.xml @@ -1,24 +1,22 @@ - + - + ./tests/Browser - - - ./app - - diff --git a/tests/BeatmapsetDisqualifyNotificationsTest.php b/tests/BeatmapsetDisqualifyNotificationsTest.php index ef412eba449..52cdb16a05f 100644 --- a/tests/BeatmapsetDisqualifyNotificationsTest.php +++ b/tests/BeatmapsetDisqualifyNotificationsTest.php @@ -128,7 +128,7 @@ public function testNotificationNotSentIfNotificationOptionsNotEnabled() } #endregion - public function booleanDataProvider() + public static function booleanDataProvider() { return [ [true], diff --git a/tests/BroadcastNotificationTest.php b/tests/BroadcastNotificationTest.php index 9631545cb8c..13629c3bdcb 100644 --- a/tests/BroadcastNotificationTest.php +++ b/tests/BroadcastNotificationTest.php @@ -102,12 +102,9 @@ public function testSendNotificationWithOptions($details) } } - public function notificationJobClassesDataProvider() + public static function notificationJobClassesDataProvider() { - $this->refreshApplication(); - - $path = app()->path('Jobs/Notifications'); - $files = Finder::create()->files()->in($path)->sortByName(); + $files = Finder::create()->files()->in(__DIR__.'/../app/Jobs/Notifications')->sortByName(); foreach ($files as $file) { $baseName = $file->getBasename(".{$file->getExtension()}"); $classes[] = ["\\App\\Jobs\\Notifications\\{$baseName}"]; @@ -116,7 +113,7 @@ public function notificationJobClassesDataProvider() return $classes; } - public function notificationNamesDataProvider() + public static function notificationNamesDataProvider() { // TODO: move notification names to different class instead of filtering $constants = collect((new ReflectionClass(Notification::class))->getReflectionConstants()) @@ -129,7 +126,7 @@ public function notificationNamesDataProvider() return $constants->map(fn (ReflectionClassConstant $constant) => [$constant->getValue()])->all(); } - public function userNotificationDetailsDataProvider() + public static function userNotificationDetailsDataProvider() { return [ [null], // for testing defaults. diff --git a/tests/Browser/SanityTest.php b/tests/Browser/SanityTest.php index 1cf3bf25431..fa7e02e3059 100644 --- a/tests/Browser/SanityTest.php +++ b/tests/Browser/SanityTest.php @@ -308,7 +308,7 @@ private static function output($text) } } - public function routesDataProvider() + public static function routesDataProvider() { static $bypass = [ '__clockwork', @@ -321,7 +321,7 @@ public function routesDataProvider() ]; static $types = ['user', 'guest']; - $this->refreshApplication(); + static::createApp(); $data = []; foreach (app()->routes->get('GET') as $uri => $route) { diff --git a/tests/Commands/EsIndexScoresQueueTest.php b/tests/Commands/EsIndexScoresQueueTest.php index d3cb2b9bd50..e2b288f5c6c 100644 --- a/tests/Commands/EsIndexScoresQueueTest.php +++ b/tests/Commands/EsIndexScoresQueueTest.php @@ -63,7 +63,7 @@ public function testQueueScores(callable $setUp, array|callable $params, int $ch ); } - public function dataProviderForTestParameterValidity(): array + public static function dataProviderForTestParameterValidity(): array { return [ [[], false], @@ -83,7 +83,7 @@ public function dataProviderForTestParameterValidity(): array ]; } - public function dataProviderForTestQueueScores(): array + public static function dataProviderForTestQueueScores(): array { $userId = 0; $setUp = function () use ($userId) { diff --git a/tests/Commands/ModdingRankCommandTest.php b/tests/Commands/ModdingRankCommandTest.php index 7866b229b01..2aa7ad92f74 100644 --- a/tests/Commands/ModdingRankCommandTest.php +++ b/tests/Commands/ModdingRankCommandTest.php @@ -104,7 +104,7 @@ public function testRankQuotaSeparateRuleset(): void } - public function rankDataProvider() + public static function rankDataProvider() { // 1 day ago isn't used because it might or might not be equal to the cutoff depending on how fast it runs. return [ @@ -113,7 +113,7 @@ public function rankDataProvider() ]; } - public function rankHybridDataProvider() + public static function rankHybridDataProvider() { return [ // hybrid counts as ruleset with lowest enum value diff --git a/tests/Controllers/AccountControllerTest.php b/tests/Controllers/AccountControllerTest.php index a9fb9fb177f..3d54f1fe4d8 100644 --- a/tests/Controllers/AccountControllerTest.php +++ b/tests/Controllers/AccountControllerTest.php @@ -231,7 +231,7 @@ public function testUpdatePasswordWeakPassword() ->assertStatus(422); } - public function dataProviderForUpdateCountry(): array + public static function dataProviderForUpdateCountry(): array { return [ ['_A', '_A', true], diff --git a/tests/Controllers/BeatmapDiscussionsControllerTest.php b/tests/Controllers/BeatmapDiscussionsControllerTest.php index 4a0b855a728..13bf667b1dc 100644 --- a/tests/Controllers/BeatmapDiscussionsControllerTest.php +++ b/tests/Controllers/BeatmapDiscussionsControllerTest.php @@ -198,7 +198,7 @@ public function testPostReviewDocumentValidWithIssues() $this->assertSame($discussionPostCount + 3, BeatmapDiscussionPost::count()); } - public function putVoteDataProvider() + public static function putVoteDataProvider() { return [ ['graveyard', 403, 0], @@ -211,7 +211,7 @@ public function putVoteDataProvider() ]; } - public function putVoteAgainDataProvider() + public static function putVoteAgainDataProvider() { return [ 'voting again has no effect' => ['1', 0], @@ -219,7 +219,7 @@ public function putVoteAgainDataProvider() ]; } - public function putVoteChangeToDownDataProvider() + public static function putVoteChangeToDownDataProvider() { return [ 'bng can change to down vote' => ['bng', 200, -2], @@ -227,7 +227,7 @@ public function putVoteChangeToDownDataProvider() ]; } - public function putVoteDownDataProvider() + public static function putVoteDownDataProvider() { return [ 'bng can down vote' => ['bng', 200, 1, -1], diff --git a/tests/Controllers/BeatmapsControllerSoloScoresTest.php b/tests/Controllers/BeatmapsControllerSoloScoresTest.php index 18354dee94e..eedbaf7c9f8 100644 --- a/tests/Controllers/BeatmapsControllerSoloScoresTest.php +++ b/tests/Controllers/BeatmapsControllerSoloScoresTest.php @@ -202,7 +202,7 @@ public function testQuery(array $scoreKeys, array $params) } } - public function dataProviderForTestQuery(): array + public static function dataProviderForTestQuery(): array { return [ 'no parameters' => [[ diff --git a/tests/Controllers/BeatmapsControllerTest.php b/tests/Controllers/BeatmapsControllerTest.php index c15694a30c7..629272e79fd 100644 --- a/tests/Controllers/BeatmapsControllerTest.php +++ b/tests/Controllers/BeatmapsControllerTest.php @@ -594,7 +594,7 @@ public function testUpdateOwnerSameOwner(): void $this->assertSame($beatmapsetEventCount, BeatmapsetEvent::count()); } - public function dataProviderForTestLookupForApi(): array + public static function dataProviderForTestLookupForApi(): array { return [ 'checksum' => ['checksum', fn (Beatmap $b) => $b->checksum], @@ -603,7 +603,7 @@ public function dataProviderForTestLookupForApi(): array ]; } - public function dataProviderForTestUpdateOwnerLoved(): array + public static function dataProviderForTestUpdateOwnerLoved(): array { return [ [Beatmapset::STATES['graveyard'], true], diff --git a/tests/Controllers/BeatmapsetsControllerTest.php b/tests/Controllers/BeatmapsetsControllerTest.php index b26b32d4b99..54f8c79d629 100644 --- a/tests/Controllers/BeatmapsetsControllerTest.php +++ b/tests/Controllers/BeatmapsetsControllerTest.php @@ -330,14 +330,14 @@ public function testBeatmapsetUpdateTags(string $userGroupOrOwner, bool $ok): vo $this->assertSame($expectedTags, $beatmapset->fresh()->tags); } - public function beatmapsetStatesDataProvider() + public static function beatmapsetStatesDataProvider() { return array_map(function ($state) { return [$state]; }, array_keys(Beatmapset::STATES)); } - public function dataProviderForTestBeatmapsetUpdateOffset(): array + public static function dataProviderForTestBeatmapsetUpdateOffset(): array { return [ ['admin', true], @@ -349,7 +349,7 @@ public function dataProviderForTestBeatmapsetUpdateOffset(): array ]; } - public function dataProviderForTestBeatmapsetUpdateTags(): array + public static function dataProviderForTestBeatmapsetUpdateTags(): array { return [ ['admin', true], @@ -361,7 +361,7 @@ public function dataProviderForTestBeatmapsetUpdateTags(): array ]; } - public function dataProviderForTestBeatmapsetUpdateDescriptionAsOwner(): array + public static function dataProviderForTestBeatmapsetUpdateDescriptionAsOwner(): array { return [ [false, null, true], diff --git a/tests/Controllers/Chat/ChannelsControllerTest.php b/tests/Controllers/Chat/ChannelsControllerTest.php index dbd7a5b4813..dac9bbc3b51 100644 --- a/tests/Controllers/Chat/ChannelsControllerTest.php +++ b/tests/Controllers/Chat/ChannelsControllerTest.php @@ -378,7 +378,7 @@ public function testChannelLeavePublicWhenGuest() // fail //endregion - public function dataProvider() + public static function dataProvider() { return [ ['private', false], diff --git a/tests/Controllers/Chat/ChatControllerTest.php b/tests/Controllers/Chat/ChatControllerTest.php index 68a55a212cf..2500ff03332 100644 --- a/tests/Controllers/Chat/ChatControllerTest.php +++ b/tests/Controllers/Chat/ChatControllerTest.php @@ -288,7 +288,7 @@ public function testChatUpdatesJoinChannel() //endregion - public function createPmWithAuthorizedGrantDataProvider() + public static function createPmWithAuthorizedGrantDataProvider() { return [ [['*'], 200], @@ -297,7 +297,7 @@ public function createPmWithAuthorizedGrantDataProvider() ]; } - public function createPmWithClientCredentialsDataProvider() + public static function createPmWithClientCredentialsDataProvider() { return [ // TODO: need to add test that validates auth guard calls Token::validate @@ -305,7 +305,7 @@ public function createPmWithClientCredentialsDataProvider() ]; } - public function createPmWithClientCredentialsBotGroupDataProvider() + public static function createPmWithClientCredentialsBotGroupDataProvider() { return [ [['chat.write', 'delegate'], 200], diff --git a/tests/Controllers/CommentsControllerTest.php b/tests/Controllers/CommentsControllerTest.php index 5b2247cab0b..e7a9e9bd281 100644 --- a/tests/Controllers/CommentsControllerTest.php +++ b/tests/Controllers/CommentsControllerTest.php @@ -250,7 +250,7 @@ public function testApiRequiresAuthentication($method, $routeName) ->assertUnauthorized(); } - public function apiRequiresAuthenticationDataProvider() + public static function apiRequiresAuthenticationDataProvider() { return [ ['DELETE', 'comments.vote'], @@ -270,7 +270,7 @@ public function apiRequiresAuthenticationDataProvider() * - Whether the commentable already has a pinned comment * - Whether pinning should be allowed */ - public function pinPermissionsDataProvider(): array + public static function pinPermissionsDataProvider(): array { return [ ['admin', true, true, true, true, true], diff --git a/tests/Controllers/InterOp/UserGroupsControllerTest.php b/tests/Controllers/InterOp/UserGroupsControllerTest.php index 8bf9537dfcb..c7084fe3c54 100644 --- a/tests/Controllers/InterOp/UserGroupsControllerTest.php +++ b/tests/Controllers/InterOp/UserGroupsControllerTest.php @@ -370,7 +370,7 @@ public function testWithInvalidActor(string $type, string $method, string $route ->assertStatus(404); } - public function userGroupRoutesDataProvider(): array + public static function userGroupRoutesDataProvider(): array { return [ 'add' => diff --git a/tests/Controllers/LegacyApiKeyControllerTest.php b/tests/Controllers/LegacyApiKeyControllerTest.php index 562ede9b84a..268541b7e16 100644 --- a/tests/Controllers/LegacyApiKeyControllerTest.php +++ b/tests/Controllers/LegacyApiKeyControllerTest.php @@ -98,7 +98,7 @@ public function testStoreGuest(): void $this->post(route('legacy-api-key.store'))->assertStatus(401); } - public function dataProviderForStoreWithInvalidParams(): array + public static function dataProviderForStoreWithInvalidParams(): array { return [ [[ diff --git a/tests/Controllers/Multiplayer/Rooms/Playlist/ScoresControllerTest.php b/tests/Controllers/Multiplayer/Rooms/Playlist/ScoresControllerTest.php index a6bb4cf1273..418bba27501 100644 --- a/tests/Controllers/Multiplayer/Rooms/Playlist/ScoresControllerTest.php +++ b/tests/Controllers/Multiplayer/Rooms/Playlist/ScoresControllerTest.php @@ -145,7 +145,7 @@ public function testUpdate($bodyParams, $status) $this->json('PUT', $url, $bodyParams)->assertStatus($status); } - public function dataProviderForTestStore() + public static function dataProviderForTestStore() { return [ 'ok' => [true, true, 200], @@ -155,7 +155,7 @@ public function dataProviderForTestStore() ]; } - public function dataProviderForTestUpdate() + public static function dataProviderForTestUpdate() { static $validBodyParams = [ 'accuracy' => 1, diff --git a/tests/Controllers/Multiplayer/RoomsControllerTest.php b/tests/Controllers/Multiplayer/RoomsControllerTest.php index b952db44d75..c96d66a55b9 100644 --- a/tests/Controllers/Multiplayer/RoomsControllerTest.php +++ b/tests/Controllers/Multiplayer/RoomsControllerTest.php @@ -15,6 +15,7 @@ use App\Models\Multiplayer\UserScoreAggregate; use App\Models\OAuth\Token; use App\Models\User; +use Illuminate\Support\Arr; use Tests\TestCase; class RoomsControllerTest extends TestCase @@ -428,10 +429,10 @@ public function testJoinWithPassword() $this->assertSame($initialUserChannelCount + 1, UserChannel::count()); } - public function dataProviderForTestStoreWithInvalidPlayableMods(): array + public static function dataProviderForTestStoreWithInvalidPlayableMods(): array { $ret = []; - foreach ([array_rand_val(Room::REALTIME_TYPES), Room::PLAYLIST_TYPE] as $type) { + foreach ([Arr::random(Room::REALTIME_TYPES), Room::PLAYLIST_TYPE] as $type) { foreach (['allowed', 'required'] as $modType) { $ret[] = [$type, $modType]; } @@ -440,18 +441,18 @@ public function dataProviderForTestStoreWithInvalidPlayableMods(): array return $ret; } - public function dataProviderForTestStoreWithInvalidRealtimeAllowedMods(): array + public static function dataProviderForTestStoreWithInvalidRealtimeAllowedMods(): array { return [ - [array_rand_val(Room::REALTIME_TYPES), false], + [Arr::random(Room::REALTIME_TYPES), false], [Room::PLAYLIST_TYPE, true], ]; } - public function dataProviderForTestStoreWithInvalidRealtimeMods(): array + public static function dataProviderForTestStoreWithInvalidRealtimeMods(): array { return [ - [array_rand_val(Room::REALTIME_TYPES), false], + [Arr::random(Room::REALTIME_TYPES), false], [Room::PLAYLIST_TYPE, true], ]; } diff --git a/tests/Controllers/OAuth/ClientsControllerTest.php b/tests/Controllers/OAuth/ClientsControllerTest.php index 851d489fcd5..5eb3c2ddef3 100644 --- a/tests/Controllers/OAuth/ClientsControllerTest.php +++ b/tests/Controllers/OAuth/ClientsControllerTest.php @@ -122,7 +122,7 @@ public function testOnlyRedirectIsUpdated() $this->assertSame('https://nowhere.local', $this->client->redirect); } - public function emptyStringsTestDataProvider() + public static function emptyStringsTestDataProvider() { return [ [null, 'https://nowhere.local'], diff --git a/tests/Controllers/ScoreTokensControllerTest.php b/tests/Controllers/ScoreTokensControllerTest.php index 70c4579c4a8..93910a7c6ca 100644 --- a/tests/Controllers/ScoreTokensControllerTest.php +++ b/tests/Controllers/ScoreTokensControllerTest.php @@ -90,7 +90,7 @@ public function testStoreInvalidParameter(string $paramKey, ?string $paramValue, ]); } - public function dataProviderForTestStore(): array + public static function dataProviderForTestStore(): array { return [ ['deleted', 404], @@ -101,7 +101,7 @@ public function dataProviderForTestStore(): array ]; } - public function dataProviderForTestStoreInvalidParameter(): array + public static function dataProviderForTestStoreInvalidParameter(): array { return [ 'invalid build hash' => ['version_hash', md5('invalid_'), 422], diff --git a/tests/Controllers/UsersControllerTest.php b/tests/Controllers/UsersControllerTest.php index 0566992e82b..b436451cce7 100644 --- a/tests/Controllers/UsersControllerTest.php +++ b/tests/Controllers/UsersControllerTest.php @@ -332,7 +332,7 @@ public function testUsernameAtPrefixedRedirectToIdForApi() ->assertSuccessful(); } - public function dataProviderForStoreWebInvalidParams(): array + public static function dataProviderForStoreWebInvalidParams(): array { return [ ['user1', 'user@email.com', 'user@email.com', 'short', 'short'], diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index 40bb7fc1a68..80a945924b9 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -9,6 +9,14 @@ trait CreatesApplication { + public static function createApp() + { + $app = require __DIR__.'/../bootstrap/app.php'; + $app->make(Kernel::class)->bootstrap(); + + return $app; + } + /** * Creates the application. * @@ -16,9 +24,6 @@ trait CreatesApplication */ public function createApplication() { - $app = require __DIR__.'/../bootstrap/app.php'; - $app->make(Kernel::class)->bootstrap(); - - return $app; + return static::createApp(); } } diff --git a/tests/HelpersTest.php b/tests/HelpersTest.php index c25fbce22d3..5946c848a6f 100644 --- a/tests/HelpersTest.php +++ b/tests/HelpersTest.php @@ -56,7 +56,7 @@ public function testIsSqlUniqueException(): void $this->assertTrue(is_sql_unique_exception($exception)); } - public function dataForClassWithModifiers(): array + public static function dataForClassWithModifiers(): array { return [ 'no modifiers' => @@ -84,7 +84,7 @@ public function dataForClassWithModifiers(): array ]; } - public function dataForGetStringSplit(): array + public static function dataForGetStringSplit(): array { return [ ["hello\nworld\n!", ['hello', 'world', '!']], diff --git a/tests/Libraries/BBCodeForDBTest.php b/tests/Libraries/BBCodeForDBTest.php index cbe3074af1c..7921906aabf 100644 --- a/tests/Libraries/BBCodeForDBTest.php +++ b/tests/Libraries/BBCodeForDBTest.php @@ -72,9 +72,9 @@ public function testProfileInvalidUser() $this->assertSame($expectedOutput, $output); } - public function examples() + public static function examples() { - return $this->fileList(__DIR__.'/bbcode_examples', '.base.txt'); + return static::fileList(__DIR__.'/bbcode_examples', '.base.txt'); } protected function setUp(): void diff --git a/tests/Libraries/BBCodeFromDBTest.php b/tests/Libraries/BBCodeFromDBTest.php index 9ce97543465..f7e91809879 100644 --- a/tests/Libraries/BBCodeFromDBTest.php +++ b/tests/Libraries/BBCodeFromDBTest.php @@ -40,14 +40,14 @@ public function testRemoveBlockQuotes($name, $path) $this->assertStringEqualsFile($expectedFilePath, $text); } - public function examples() + public static function examples() { - return $this->fileList(__DIR__.'/bbcode_examples', '.db.txt'); + return static::fileList(__DIR__.'/bbcode_examples', '.db.txt'); } - public function removeQuoteExamples() + public static function removeQuoteExamples() { - return $this->fileList(__DIR__.'/bbcode_examples/remove_quotes', '.db.txt'); + return static::fileList(__DIR__.'/bbcode_examples/remove_quotes', '.db.txt'); } protected function setUp(): void diff --git a/tests/Libraries/BeatmapsetDiscussion/DiscussionTest.php b/tests/Libraries/BeatmapsetDiscussion/DiscussionTest.php index 52883b4899d..c1b8105ae87 100644 --- a/tests/Libraries/BeatmapsetDiscussion/DiscussionTest.php +++ b/tests/Libraries/BeatmapsetDiscussion/DiscussionTest.php @@ -35,11 +35,11 @@ class DiscussionTest extends TestCase /** * @dataProvider minPlaysVerificationDataProvider */ - public function testMinPlaysVerification(?int $minPlays, bool $verified, bool $success) + public function testMinPlaysVerification(\Closure $minPlays, bool $verified, bool $success) { config()->set('osu.user.post_action_verification', false); - $user = User::factory()->withPlays($minPlays)->create(); + $user = User::factory()->withPlays($minPlays())->create(); $beatmapset = $this->beatmapsetFactory()->create(); $beatmapset->watches()->create(['user_id' => User::factory()->create()->getKey()]); @@ -284,17 +284,17 @@ public function testProblemOnQualifiedBeatmapsetModesNotification(string $mode, //endregion - public function minPlaysVerificationDataProvider() + public static function minPlaysVerificationDataProvider() { return [ - [config('osu.user.min_plays_for_posting') - 1, false, false], - [config('osu.user.min_plays_for_posting') - 1, true, true], - [null, false, true], - [null, true, true], + [fn () => config('osu.user.min_plays_for_posting') - 1, false, false], + [fn () => config('osu.user.min_plays_for_posting') - 1, true, true], + [fn () => null, false, true], + [fn () => null, true, true], ]; } - public function problemOnQualifiedBeatmapsetDataProvider() + public static function problemOnQualifiedBeatmapsetDataProvider() { return [ ['pending', 'Event::assertNotDispatched'], @@ -302,7 +302,7 @@ public function problemOnQualifiedBeatmapsetDataProvider() ]; } - public function problemOnQualifiedBeatmapsetModesNotificationDataProvider() + public static function problemOnQualifiedBeatmapsetModesNotificationDataProvider() { return [ 'with matching notification mode' => ['osu', ['osu'], true], @@ -310,7 +310,7 @@ public function problemOnQualifiedBeatmapsetModesNotificationDataProvider() ]; } - public function newDiscussionQueuesJobsDataProvider() + public static function newDiscussionQueuesJobsDataProvider() { return [ [ @@ -352,7 +352,7 @@ public function newDiscussionQueuesJobsDataProvider() ]; } - public function newMapperNoteByOtherUsersDataProvider() + public static function newMapperNoteByOtherUsersDataProvider() { return [ ['bng', true], @@ -363,7 +363,7 @@ public function newMapperNoteByOtherUsersDataProvider() ]; } - public function shouldDisqualifyOrResetNominationsDataProvider() + public static function shouldDisqualifyOrResetNominationsDataProvider() { return [ ['pending', 'bng', 'problem', true], @@ -383,7 +383,7 @@ public function shouldDisqualifyOrResetNominationsDataProvider() ]; } - public function userGroupsDataProvider() + public static function userGroupsDataProvider() { return [ ['admin'], diff --git a/tests/Libraries/BeatmapsetDiscussion/ReplyTest.php b/tests/Libraries/BeatmapsetDiscussion/ReplyTest.php index 862cc822621..be8bfa66f42 100644 --- a/tests/Libraries/BeatmapsetDiscussion/ReplyTest.php +++ b/tests/Libraries/BeatmapsetDiscussion/ReplyTest.php @@ -341,7 +341,7 @@ public function testRequestingSameResolveStateDoesNotChangeResovled() $this->assertFalse($discussion->fresh()->resolved); } - public function reopeningProblemDoesNotDisqualifyOrResetNominationsDataProvider() + public static function reopeningProblemDoesNotDisqualifyOrResetNominationsDataProvider() { return [ ['bng', 'pending'], @@ -353,7 +353,7 @@ public function reopeningProblemDoesNotDisqualifyOrResetNominationsDataProvider( ]; } - public function replyQueuesNotificationDataProviderToStarter() + public static function replyQueuesNotificationDataProviderToStarter() { return [ ['praise', false], @@ -362,7 +362,7 @@ public function replyQueuesNotificationDataProviderToStarter() ]; } - public function resolveDiscussionByStarterDataProvider() + public static function resolveDiscussionByStarterDataProvider() { return [ ['praise', false], @@ -371,7 +371,7 @@ public function resolveDiscussionByStarterDataProvider() ]; } - public function resolveDiscussionByMapperDataProvider() + public static function resolveDiscussionByMapperDataProvider() { return [ ['pending', true], @@ -379,7 +379,7 @@ public function resolveDiscussionByMapperDataProvider() ]; } - public function resolveDiscussionByOtherUsersDataProvider() + public static function resolveDiscussionByOtherUsersDataProvider() { return [ ['bng', false], @@ -390,7 +390,7 @@ public function resolveDiscussionByOtherUsersDataProvider() ]; } - public function userGroupsDataProvider() + public static function userGroupsDataProvider() { return [ ['admin'], diff --git a/tests/Libraries/BeatmapsetDiscussion/ReviewTest.php b/tests/Libraries/BeatmapsetDiscussion/ReviewTest.php index aadcff4efd9..611383ba6b1 100644 --- a/tests/Libraries/BeatmapsetDiscussion/ReviewTest.php +++ b/tests/Libraries/BeatmapsetDiscussion/ReviewTest.php @@ -717,7 +717,7 @@ public function testUpdateDocumentRemoveIssue() //endregion - public function dataProviderForQualifiedProblem() + public static function dataProviderForQualifiedProblem() { return [ ['qualified', true], diff --git a/tests/Libraries/ChatTest.php b/tests/Libraries/ChatTest.php index 573fd5807e0..3446e73d0fe 100644 --- a/tests/Libraries/ChatTest.php +++ b/tests/Libraries/ChatTest.php @@ -265,7 +265,7 @@ public function testSendPMSecondTime() Chat::sendPrivateMessage($sender, $target, 'test message again', false); } - public function createAnnouncementApiDataProvider() + public static function createAnnouncementApiDataProvider() { return [ [null, false, false], @@ -285,7 +285,7 @@ public function createAnnouncementApiDataProvider() ]; } - public function minPlaysDataProvider() + public static function minPlaysDataProvider() { return [ 'bot group with minplays' => ['bot', true, true], @@ -295,7 +295,7 @@ public function minPlaysDataProvider() ]; } - public function sendPmFriendsOnlyGroupsDataProvider() + public static function sendPmFriendsOnlyGroupsDataProvider() { return [ ['admin', true], @@ -307,7 +307,7 @@ public function sendPmFriendsOnlyGroupsDataProvider() ]; } - public function sendPmSenderFriendsOnlyGroupsDataProvider() + public static function sendPmSenderFriendsOnlyGroupsDataProvider() { return [ // admin skip because OsuAuthorize skips the check when admin. @@ -319,7 +319,7 @@ public function sendPmSenderFriendsOnlyGroupsDataProvider() ]; } - public function verifiedDataProvider() + public static function verifiedDataProvider() { return [ [false, VerificationRequiredException::class], diff --git a/tests/Libraries/Fulfillments/SupporterTagFulfillmentTest.php b/tests/Libraries/Fulfillments/SupporterTagFulfillmentTest.php index 8373e0c4c43..b71a696988c 100644 --- a/tests/Libraries/Fulfillments/SupporterTagFulfillmentTest.php +++ b/tests/Libraries/Fulfillments/SupporterTagFulfillmentTest.php @@ -291,7 +291,7 @@ public function testInsufficientAmountMultiple() (new SupporterTagFulfillment($this->order))->run(); } - public function boolDataProvider() + public static function boolDataProvider() { return [ [true], diff --git a/tests/Libraries/Ip2AsnTest.php b/tests/Libraries/Ip2AsnTest.php index 72b65fe1c03..1af58a59343 100644 --- a/tests/Libraries/Ip2AsnTest.php +++ b/tests/Libraries/Ip2AsnTest.php @@ -18,7 +18,7 @@ public function testLookup(string $ip, string $asn) $this->assertSame((new Ip2Asn())->lookup($ip), $asn); } - public function dataProviderForLookup(): array + public static function dataProviderForLookup(): array { return [ 'cloudflare 1' => ['2606:4700::6810:85e5', '13335'], diff --git a/tests/Libraries/Markdown/ChatMarkdownTest.php b/tests/Libraries/Markdown/ChatMarkdownTest.php index 718d002cb4b..e0431b8f9a8 100644 --- a/tests/Libraries/Markdown/ChatMarkdownTest.php +++ b/tests/Libraries/Markdown/ChatMarkdownTest.php @@ -22,9 +22,9 @@ public function testChat($name, $path) ); } - public function chatExamples() + public static function chatExamples() { - return $this->fileList(__DIR__.'/chat_markdown_examples', '.md'); + return static::fileList(__DIR__.'/chat_markdown_examples', '.md'); } private function loadOutputTest(string $name, string $path) diff --git a/tests/Libraries/Markdown/ProcessorTest.php b/tests/Libraries/Markdown/ProcessorTest.php index 120dc2a9efd..c29398bcf03 100644 --- a/tests/Libraries/Markdown/ProcessorTest.php +++ b/tests/Libraries/Markdown/ProcessorTest.php @@ -52,14 +52,14 @@ public function testTocImage() $this->assertSame('some header', $parsed['toc']['some-header']['title']); } - public function htmlExamples() + public static function htmlExamples() { - return $this->fileList(__DIR__.'/html_markdown_examples', '.md'); + return static::fileList(__DIR__.'/html_markdown_examples', '.md'); } - public function indexableExamples() + public static function indexableExamples() { - return $this->fileList(__DIR__.'/indexable_markdown_examples', '.md'); + return static::fileList(__DIR__.'/indexable_markdown_examples', '.md'); } private function loadOutputTest(string $name, string $path, string $extension) diff --git a/tests/Libraries/ModsTest.php b/tests/Libraries/ModsTest.php index 8c8d3c75687..9e2fcb9e885 100644 --- a/tests/Libraries/ModsTest.php +++ b/tests/Libraries/ModsTest.php @@ -100,7 +100,7 @@ public function testValidateSelection(Ruleset $ruleset, $modCombo, $isValid) } } - public function modCombos() + public static function modCombos() { return [ // valid @@ -140,7 +140,7 @@ public function modCombos() ]; } - public function modComboExclusives() + public static function modComboExclusives() { return [ // non-exclusive required mods and no allowed mods diff --git a/tests/Libraries/Search/BeatmapsetQueryParserTest.php b/tests/Libraries/Search/BeatmapsetQueryParserTest.php index d2883bb4cca..7d1ea0587d0 100644 --- a/tests/Libraries/Search/BeatmapsetQueryParserTest.php +++ b/tests/Libraries/Search/BeatmapsetQueryParserTest.php @@ -19,7 +19,7 @@ public function testParse(?string $query, ?array $expected) $this->assertSame(json_encode($expected), json_encode(BeatmapsetQueryParser::parse($query))); } - public function queryDataProvider() + public static function queryDataProvider() { return [ // basic options diff --git a/tests/Libraries/Search/BeatmapsetSearchRequestParamsTest.php b/tests/Libraries/Search/BeatmapsetSearchRequestParamsTest.php index 28ff30fa572..5d179c3ba6f 100644 --- a/tests/Libraries/Search/BeatmapsetSearchRequestParamsTest.php +++ b/tests/Libraries/Search/BeatmapsetSearchRequestParamsTest.php @@ -59,7 +59,7 @@ public function testCursorsGuest(?string $sort, ?array $cursor, bool $throws, ?a $this->assertSame($expected, $searchAfter); } - public function cursorsDataProvider() + public static function cursorsDataProvider() { return [ [null, null, false, null], @@ -74,7 +74,7 @@ public function cursorsDataProvider() ]; } - public function cursorsGuestDataProvider() + public static function cursorsGuestDataProvider() { return [ [null, null, false, null], diff --git a/tests/Libraries/SignedRandomStringTest.php b/tests/Libraries/SignedRandomStringTest.php index b917154a0f6..a3244d58974 100644 --- a/tests/Libraries/SignedRandomStringTest.php +++ b/tests/Libraries/SignedRandomStringTest.php @@ -7,6 +7,7 @@ namespace Tests\Libraries; +use App\Libraries\Base64Url; use App\Libraries\SignedRandomString; use Tests\TestCase; @@ -25,12 +26,12 @@ public function testIsValidInvalid(string $value): void $this->assertFalse(SignedRandomString::isValid($value)); } - public function dataProviderForTestIsValidInvalid(): array + public static function dataProviderForTestIsValidInvalid(): array { return [ ['invalid'], [''], - [base64url_encode('test')], + [Base64Url::encode('test')], ]; } } diff --git a/tests/Libraries/UsernameValidationTest.php b/tests/Libraries/UsernameValidationTest.php index 7b97bee4877..7b491c5a12c 100644 --- a/tests/Libraries/UsernameValidationTest.php +++ b/tests/Libraries/UsernameValidationTest.php @@ -179,7 +179,7 @@ public function testValidateUsersOfUsernameHasGuestBeatmaps(string $state, bool * - Beatmap or beatmapset state * - Whether the username should be available */ - public function usernameAvailabilityWithBeatmapStateDataProvider(): array + public static function usernameAvailabilityWithBeatmapStateDataProvider(): array { return [ ['graveyard', true], @@ -197,7 +197,7 @@ public function usernameAvailabilityWithBeatmapStateDataProvider(): array * - Username * - Whether the username should be valid */ - public function usernameValidationDataProvider(): array + public static function usernameValidationDataProvider(): array { return [ 'alphabetic' => ['Username', true], @@ -223,7 +223,7 @@ public function usernameValidationDataProvider(): array * - Whether the user lookup should have its underscores replaced with spaces * - Whether the user lookup should return the user */ - public function usersOfUsernameLookupDataProvider(): array + public static function usersOfUsernameLookupDataProvider(): array { return [ [true, true, false], diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index b9f2328a926..07167f89330 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -145,10 +145,11 @@ public function testLocaleWebWithAcceptHeaderAndUserLang() $this->assertSame('fr', App::getLocale()); } - public function availableLocalesProvider() + public static function availableLocalesProvider() { - return array_map(function ($locale) { - return [$locale]; - }, config('app.available_locales')); + return array_map( + fn ($locale) => [$locale], + (require __DIR__.'/../config/app.php')['available_locales'], + ); } } diff --git a/tests/Middleware/RequireScopesTest.php b/tests/Middleware/RequireScopesTest.php index 053059a1f57..cbe7f8218ca 100644 --- a/tests/Middleware/RequireScopesTest.php +++ b/tests/Middleware/RequireScopesTest.php @@ -112,7 +112,7 @@ public function testUserScopes($requiredScopes, $userScopes, $expectedException) $this->assertTrue(!oauth_token()->isClientCredentials()); } - public function clientCredentialsTestDataProvider() + public static function clientCredentialsTestDataProvider() { return [ 'null is not a valid scope' => [null, MissingScopeException::class], @@ -132,7 +132,7 @@ public function clientCredentialsTestWhenAllScopeRequiredDataProvider() ]; } - public function userScopesTestDataProvider() + public static function userScopesTestDataProvider() { return [ 'All scopes' => [null, ['*'], null], diff --git a/tests/Middleware/RouteScopesTest.php b/tests/Middleware/RouteScopesTest.php index b971c4001f7..94e6e94ebe5 100644 --- a/tests/Middleware/RouteScopesTest.php +++ b/tests/Middleware/RouteScopesTest.php @@ -72,10 +72,9 @@ public function testUnscopedRequestsRequireAuthentication(string $url, string $m } } - public function routesDataProvider() + public static function routesDataProvider() { - // note that $this->app does not carry over to the tests. - $this->refreshApplication(); + static::createApp(); $data = []; @@ -103,14 +102,14 @@ public function routesDataProvider() return $data; } - public function routeScopesDataProvider() + public static function routeScopesDataProvider() { - // note that $this->app does not carry over to the tests. - $this->refreshApplication(); + static::createApp(); - return array_map(function ($route) { - return [$route]; - }, (new RouteScopesHelper())->toArray()); + return array_map( + fn ($route) => [$route], + (new RouteScopesHelper())->toArray(), + ); } private function importExpectations() diff --git a/tests/Middleware/ThrottleRequestsTest.php b/tests/Middleware/ThrottleRequestsTest.php index e329e11dbc4..f289072cc18 100644 --- a/tests/Middleware/ThrottleRequestsTest.php +++ b/tests/Middleware/ThrottleRequestsTest.php @@ -45,7 +45,7 @@ public function testThrottleMultipleRequests() ->assertHeader('X-Ratelimit-Remaining', 58); } - public function throttleDataProvider() + public static function throttleDataProvider() { return [ 'throttle' => [['throttle:60,10'], 59], diff --git a/tests/Models/BeatmapDiscussionTest.php b/tests/Models/BeatmapDiscussionTest.php index cae37a3034e..60f1bda9dde 100644 --- a/tests/Models/BeatmapDiscussionTest.php +++ b/tests/Models/BeatmapDiscussionTest.php @@ -184,7 +184,7 @@ public function testSoftDeleteOrExplode() $this->assertFalse($discussion->trashed()); } - public function validBeatmapsetStatuses() + public static function validBeatmapsetStatuses() { return array_map(function ($status) { return [camel_case($status)]; diff --git a/tests/Models/BeatmapPackUserCompletionTest.php b/tests/Models/BeatmapPackUserCompletionTest.php index 12122c1225a..253a0290951 100644 --- a/tests/Models/BeatmapPackUserCompletionTest.php +++ b/tests/Models/BeatmapPackUserCompletionTest.php @@ -58,7 +58,7 @@ public function testBasic(string $userType, ?string $packRuleset, bool $complete $this->assertSame($completed, $data['completed']); } - public function dataProviderForTestBasic(): array + public static function dataProviderForTestBasic(): array { return [ ['convertOsu', 'osu', true], diff --git a/tests/Models/BeatmapsetTest.php b/tests/Models/BeatmapsetTest.php index 5165eee3749..b9cb14bea58 100644 --- a/tests/Models/BeatmapsetTest.php +++ b/tests/Models/BeatmapsetTest.php @@ -507,7 +507,7 @@ public function testDisqualifyOrResetNominations(string $state, string $pushed) //end region - public function disqualifyOrResetNominationsDataProvider() + public static function disqualifyOrResetNominationsDataProvider() { return [ ['pending', BeatmapsetResetNominations::class], @@ -515,7 +515,7 @@ public function disqualifyOrResetNominationsDataProvider() ]; } - public function dataProviderForTestRank(): array + public static function dataProviderForTestRank(): array { return [ ['pending', false], @@ -523,7 +523,7 @@ public function dataProviderForTestRank(): array ]; } - public function rankWithOpenIssueDataProvider() + public static function rankWithOpenIssueDataProvider() { return [ ['problem'], diff --git a/tests/Models/ChangelogEntryTest.php b/tests/Models/ChangelogEntryTest.php index 74e192be98d..367da68468b 100644 --- a/tests/Models/ChangelogEntryTest.php +++ b/tests/Models/ChangelogEntryTest.php @@ -131,7 +131,7 @@ public function testIsPrivate() $this->assertTrue(ChangelogEntry::isPrivate($data)); } - public function dataForPublicMessageHtmlVisibility() + public static function dataForPublicMessageHtmlVisibility() { return [ ['Hidden', null], diff --git a/tests/Models/Chat/ChannelTest.php b/tests/Models/Chat/ChannelTest.php index 419b0355b97..372bf003d54 100644 --- a/tests/Models/Chat/ChannelTest.php +++ b/tests/Models/Chat/ChannelTest.php @@ -301,7 +301,7 @@ public function testResetMemoized() $this->assertEmpty($memoized); } - public function channelCanMessageModeratedChannelDataProvider() + public static function channelCanMessageModeratedChannelDataProvider() { return [ [null, false], @@ -313,7 +313,7 @@ public function channelCanMessageModeratedChannelDataProvider() ]; } - public function channelCanMessageWhenBlockedDataProvider() + public static function channelCanMessageWhenBlockedDataProvider() { return [ [null, false], @@ -325,7 +325,7 @@ public function channelCanMessageWhenBlockedDataProvider() ]; } - public function channelWithBlockedUserVisibilityDataProvider() + public static function channelWithBlockedUserVisibilityDataProvider() { return [ [null, false], @@ -337,7 +337,7 @@ public function channelWithBlockedUserVisibilityDataProvider() ]; } - public function leaveChannelDataProvider() + public static function leaveChannelDataProvider() { return [ ['announce', true], diff --git a/tests/Models/CommentTest.php b/tests/Models/CommentTest.php index 104a680a636..a918752b16b 100644 --- a/tests/Models/CommentTest.php +++ b/tests/Models/CommentTest.php @@ -88,7 +88,7 @@ public function testUnpinOnDelete() $this->assertFalse($comment->fresh()->pinned); } - public function commentReplyOptionDataProvider() + public static function commentReplyOptionDataProvider() { return [ [null, true], @@ -97,7 +97,7 @@ public function commentReplyOptionDataProvider() ]; } - public function dataProviderForSetCommentableInvalid() + public static function dataProviderForSetCommentableInvalid() { return [ [null, null], diff --git a/tests/Models/ContestTest.php b/tests/Models/ContestTest.php index f435f3a3c5d..acad06b2096 100644 --- a/tests/Models/ContestTest.php +++ b/tests/Models/ContestTest.php @@ -124,7 +124,7 @@ public function testShowEntryUser(bool $showVotes, ?bool $showEntryUserOption, b $this->assertSame($result, $contest->showEntryUser()); } - public function dataProviderForTestAssertVoteRequirementPlaylistBeatmapsets(): array + public static function dataProviderForTestAssertVoteRequirementPlaylistBeatmapsets(): array { return [ // when passing is required @@ -145,7 +145,7 @@ public function dataProviderForTestAssertVoteRequirementPlaylistBeatmapsets(): a ]; } - public function dataProviderForTestShowEntryUser(): array + public static function dataProviderForTestShowEntryUser(): array { return [ [false, null, false], diff --git a/tests/Models/ModelCompositePrimaryKeysTest.php b/tests/Models/ModelCompositePrimaryKeysTest.php index b379b3218b0..a09a8da799c 100644 --- a/tests/Models/ModelCompositePrimaryKeysTest.php +++ b/tests/Models/ModelCompositePrimaryKeysTest.php @@ -71,7 +71,7 @@ public function testUpdate(string $class, array $baseParams, array $item2Params, $this->assertSame($cast($check[2]), $cast($item2->fresh()->$key)); } - public function dataProviderBase() + public static function dataProviderBase() { // 0: class name // 1: base params diff --git a/tests/Models/ModelTest.php b/tests/Models/ModelTest.php index 4a0873f84a9..880c459527a 100644 --- a/tests/Models/ModelTest.php +++ b/tests/Models/ModelTest.php @@ -130,7 +130,7 @@ public function testIncrementInstanceSpecifyCount(callable $countryFn, bool $isC $anotherCountry->refresh(); } - public function dataProviderForDecrementInstance(): array + public static function dataProviderForDecrementInstance(): array { return [ [fn () => Country::factory()->create(), true], diff --git a/tests/Models/Multiplayer/RoomTest.php b/tests/Models/Multiplayer/RoomTest.php index 869ee13bed5..1a0bd1077ac 100644 --- a/tests/Models/Multiplayer/RoomTest.php +++ b/tests/Models/Multiplayer/RoomTest.php @@ -186,9 +186,10 @@ public function testCannotStartPlayedItem() (new Room())->startGame($user, $params); } - public function startGameDurationDataProvider() + public static function startGameDurationDataProvider() { static $dayMinutes = 1440; + static::createApp(); $maxDuration = config('osu.user.max_multiplayer_duration'); $maxDurationSupporter = config('osu.user.max_multiplayer_duration_supporter'); diff --git a/tests/Models/OAuth/GroupPermissionTest.php b/tests/Models/OAuth/GroupPermissionTest.php index 6a80a8e826d..936334cbff2 100644 --- a/tests/Models/OAuth/GroupPermissionTest.php +++ b/tests/Models/OAuth/GroupPermissionTest.php @@ -41,7 +41,7 @@ public function testGroupWithoutOAuth(string $group, string $method) $this->assertTrue(auth()->user()->$method()); } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', 'isAdmin', false], diff --git a/tests/Models/OAuth/TokenTest.php b/tests/Models/OAuth/TokenTest.php index 2becf0c7465..b902098d8bc 100644 --- a/tests/Models/OAuth/TokenTest.php +++ b/tests/Models/OAuth/TokenTest.php @@ -192,7 +192,7 @@ public function testRevokeRecursive() Event::assertDispatched(UserSessionEvent::class, fn (UserSessionEvent $event) => $event->action === 'logout'); } - public function authCodeChatWriteRequiresBotGroupDataProvider() + public static function authCodeChatWriteRequiresBotGroupDataProvider() { return [ [null, InvalidScopeException::class], @@ -204,7 +204,7 @@ public function authCodeChatWriteRequiresBotGroupDataProvider() ]; } - public function delegationNotAllowedScopesDataProvider() + public static function delegationNotAllowedScopesDataProvider() { return Passport::scopes() ->pluck('id') @@ -213,7 +213,7 @@ public function delegationNotAllowedScopesDataProvider() ->values(); } - public function delegationRequiredScopesDataProvider() + public static function delegationRequiredScopesDataProvider() { return [ 'chat.write requires delegation' => [['chat.write'], InvalidScopeException::class], @@ -221,7 +221,7 @@ public function delegationRequiredScopesDataProvider() ]; } - public function delegationRequiresChatBotDataProvider() + public static function delegationRequiresChatBotDataProvider() { return [ [null, InvalidScopeException::class], @@ -233,7 +233,7 @@ public function delegationRequiresChatBotDataProvider() ]; } - public function scopesDataProvider() + public static function scopesDataProvider() { return [ 'null is not a valid scope' => [null, InvalidScopeException::class], @@ -242,7 +242,7 @@ public function scopesDataProvider() ]; } - public function scopesClientCredentialsDataProvider() + public static function scopesClientCredentialsDataProvider() { return [ 'null is not a valid scope' => [null, InvalidScopeException::class], diff --git a/tests/Models/Score/ModelTest.php b/tests/Models/Score/ModelTest.php index 6b49014e6e8..524a264f508 100644 --- a/tests/Models/Score/ModelTest.php +++ b/tests/Models/Score/ModelTest.php @@ -29,7 +29,7 @@ public function testGetClassInvalidRuleset(string $ruleset) Model::getClass($ruleset); } - public function dataProviderForTestGetClassInvalidRuleset(): array + public static function dataProviderForTestGetClassInvalidRuleset(): array { return [ ['does'], diff --git a/tests/Models/Solo/ScoreEsIndexTest.php b/tests/Models/Solo/ScoreEsIndexTest.php index a0fa84260f4..2b7fcf6d117 100644 --- a/tests/Models/Solo/ScoreEsIndexTest.php +++ b/tests/Models/Solo/ScoreEsIndexTest.php @@ -125,7 +125,7 @@ public function testUserRank(string $key, ?array $params, int $rank): void $this->assertSame($rank, $score->userRank($params)); } - public function dataProviderForTestUserRank(): array + public static function dataProviderForTestUserRank(): array { return [ ['user', null, 4], diff --git a/tests/Models/Store/OrderItemTest.php b/tests/Models/Store/OrderItemTest.php index b4b1db8c1a3..44afd698716 100644 --- a/tests/Models/Store/OrderItemTest.php +++ b/tests/Models/Store/OrderItemTest.php @@ -104,7 +104,7 @@ public function testReleaseWhenStockIsZero() $this->assertSame($product->stock, 0); } - public function deleteDataProvider() + public static function deleteDataProvider() { return [ ['checkout', InvariantException::class], diff --git a/tests/Models/UserNotificationTest.php b/tests/Models/UserNotificationTest.php index 6d740e8cdcb..15ce3c1751d 100644 --- a/tests/Models/UserNotificationTest.php +++ b/tests/Models/UserNotificationTest.php @@ -178,7 +178,7 @@ public function testScopeHasMailDelivery() ); } - public function deliveryMaskDataProvider() + public static function deliveryMaskDataProvider() { return [ [0, 'mail', false], diff --git a/tests/Models/UserReportTest.php b/tests/Models/UserReportTest.php index b09fb3f6be6..3da5be0efe7 100644 --- a/tests/Models/UserReportTest.php +++ b/tests/Models/UserReportTest.php @@ -192,7 +192,7 @@ public function testReportableNotificationEndpoint(string $class): void $this->assertTrue(true, 'should not fail getting notification routing url'); } - public function reportableClasses(): array + public static function reportableClasses(): array { $reportables = []; diff --git a/tests/Models/UserStatistics/ModelTest.php b/tests/Models/UserStatistics/ModelTest.php index ef67d2ad3de..d61baaa7a57 100644 --- a/tests/Models/UserStatistics/ModelTest.php +++ b/tests/Models/UserStatistics/ModelTest.php @@ -30,7 +30,7 @@ public function testGetClassByThrowsExceptionIfModeDoesNotExist($mode, $variant) Model::getClass($mode, $variant); } - public function invalidModes() + public static function invalidModes() { return [ ['does', null], @@ -41,7 +41,7 @@ public function invalidModes() ]; } - public function validModes() + public static function validModes() { $modes = []; diff --git a/tests/Models/UserTest.php b/tests/Models/UserTest.php index 9bdf91dd3b0..3cc898371b7 100644 --- a/tests/Models/UserTest.php +++ b/tests/Models/UserTest.php @@ -104,7 +104,7 @@ public function testValidDiscordUsername(string $username, bool $valid) } } - public function dataProviderForAttributeTwitter(): array + public static function dataProviderForAttributeTwitter(): array { return [ ['@hello', 'hello'], @@ -115,7 +115,7 @@ public function dataProviderForAttributeTwitter(): array ]; } - public function dataProviderValidDiscordUsername(): array + public static function dataProviderValidDiscordUsername(): array { return [ ['username', true], diff --git a/tests/OAuthAuthCodeRequestTest.php b/tests/OAuthAuthCodeRequestTest.php index 59b4492ca25..d6b3940f7dc 100644 --- a/tests/OAuthAuthCodeRequestTest.php +++ b/tests/OAuthAuthCodeRequestTest.php @@ -49,7 +49,7 @@ public function testNonBotClientCannotRequestChatWriteScope() ->assertStatus(400); } - public function botClientDataProvider() + public static function botClientDataProvider() { return [ 'cannot request delegation with auth_code' => ['delegate', false], diff --git a/tests/OAuthClientCredentialsRequestTest.php b/tests/OAuthClientCredentialsRequestTest.php index 23a97001eef..f5830976a87 100644 --- a/tests/OAuthClientCredentialsRequestTest.php +++ b/tests/OAuthClientCredentialsRequestTest.php @@ -48,7 +48,7 @@ public function testNonBotRequestingScope($scope, $status) ->assertStatus($status); } - public function botRequestingScopeDataProvider() + public static function botRequestingScopeDataProvider() { return [ '* cannot be requested' => ['*', 400], @@ -60,7 +60,7 @@ public function botRequestingScopeDataProvider() ]; } - public function nonBotRequestingScopeDataProvider() + public static function nonBotRequestingScopeDataProvider() { return [ '* cannot be requested' => ['*', 400], diff --git a/tests/Providers/AuthServiceProviderTest.php b/tests/Providers/AuthServiceProviderTest.php index 1528663567f..1e2b7bde0f8 100644 --- a/tests/Providers/AuthServiceProviderTest.php +++ b/tests/Providers/AuthServiceProviderTest.php @@ -37,7 +37,7 @@ public function testPassportDefaultRoutesNotRegistered() } } - public function oauthRoutesRegisteredDataProvider() + public static function oauthRoutesRegisteredDataProvider() { return [ ['oauth/authorize', 'GET', AuthorizationController::class.'@authorize'], diff --git a/tests/TestCase.php b/tests/TestCase.php index fb3b10e1670..ab6f5838adf 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -34,13 +34,21 @@ class TestCase extends BaseTestCase public static function withDbAccess(callable $callback): void { - $db = (new static())->createApplication()->make('db'); + $db = static::createApp()->make('db'); $callback(); static::resetAppDb($db); } + protected static function fileList($path, $suffix) + { + return array_map( + fn ($file) => [basename($file, $suffix), $path], + glob("{$path}/*{$suffix}"), + ); + } + protected static function reindexScores() { $search = new ScoreSearch(); @@ -73,7 +81,7 @@ protected static function resetAppDb(DatabaseManager $database): void protected array $expectedCountsCallbacks = []; - public function regularOAuthScopesDataProvider() + public static function regularOAuthScopesDataProvider() { $data = []; @@ -267,13 +275,6 @@ protected function expectCountChange(callable $callback, int $change, string $me ]; } - protected function fileList($path, $suffix) - { - return array_map(function ($file) use ($path, $suffix) { - return [basename($file, $suffix), $path]; - }, glob("{$path}/*{$suffix}")); - } - protected function inReceivers(Model $model, NewPrivateNotificationEvent|BroadcastNotificationBase $obj): bool { return in_array($model->getKey(), $obj->getReceiverIds(), true); diff --git a/tests/Transformers/BeatmapDiscussionPostTransformerTest.php b/tests/Transformers/BeatmapDiscussionPostTransformerTest.php index c5b3edd86e5..0fb906c46f9 100644 --- a/tests/Transformers/BeatmapDiscussionPostTransformerTest.php +++ b/tests/Transformers/BeatmapDiscussionPostTransformerTest.php @@ -48,7 +48,7 @@ public function testWithoutOAuth(?string $groupIdentifier, bool $visible) } } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/BeatmapDiscussionTransformerTest.php b/tests/Transformers/BeatmapDiscussionTransformerTest.php index f9d855bd2d8..2915f3d660c 100644 --- a/tests/Transformers/BeatmapDiscussionTransformerTest.php +++ b/tests/Transformers/BeatmapDiscussionTransformerTest.php @@ -44,7 +44,7 @@ public function testWithoutOAuth(?string $groupIdentifier, bool $visible) } } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/BeatmapTransformerTest.php b/tests/Transformers/BeatmapTransformerTest.php index 6a2d208cfcc..d00a635d9c8 100644 --- a/tests/Transformers/BeatmapTransformerTest.php +++ b/tests/Transformers/BeatmapTransformerTest.php @@ -45,7 +45,7 @@ public function testWithoutOAuth(?string $groupIdentifier, bool $visible) } } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/BeatmapsetCompactTransformerTest.php b/tests/Transformers/BeatmapsetCompactTransformerTest.php index 6750dd5dd13..d25cb152fa1 100644 --- a/tests/Transformers/BeatmapsetCompactTransformerTest.php +++ b/tests/Transformers/BeatmapsetCompactTransformerTest.php @@ -64,7 +64,7 @@ public function testPropertyIsVisibleWithoutOAuth(string $property) $this->assertArrayHasKey($property, $json); } - public function propertyPermissionsDataProvider() + public static function propertyPermissionsDataProvider() { $data = []; $transformer = new BeatmapsetCompactTransformer(); diff --git a/tests/Transformers/BeatmapsetDescriptionTransformerTest.php b/tests/Transformers/BeatmapsetDescriptionTransformerTest.php index 0177356af51..dd7b5f221ad 100644 --- a/tests/Transformers/BeatmapsetDescriptionTransformerTest.php +++ b/tests/Transformers/BeatmapsetDescriptionTransformerTest.php @@ -72,7 +72,7 @@ public function testUserIsNotMapper() $this->assertArrayNotHasKey('bbcode', $json); } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/BeatmapsetEventTransformerTest.php b/tests/Transformers/BeatmapsetEventTransformerTest.php index d83590199bb..1a77bb00d8c 100644 --- a/tests/Transformers/BeatmapsetEventTransformerTest.php +++ b/tests/Transformers/BeatmapsetEventTransformerTest.php @@ -57,7 +57,7 @@ public function testWithoutOAuth(?string $groupIdentifier, string $eventType, bo } } - public function dataProvider() + public static function dataProvider() { // one event type of each priviledge type. return [ diff --git a/tests/Transformers/BeatmapsetTransformerTest.php b/tests/Transformers/BeatmapsetTransformerTest.php index 5af0c769d60..d6bf00c3fc3 100644 --- a/tests/Transformers/BeatmapsetTransformerTest.php +++ b/tests/Transformers/BeatmapsetTransformerTest.php @@ -43,7 +43,7 @@ public function testDeletedBeatmapsetGroupPermissionsWithoutOAuth(?string $group } } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/CommentTransformerTest.php b/tests/Transformers/CommentTransformerTest.php index 812ddac17a5..40b6b187d53 100644 --- a/tests/Transformers/CommentTransformerTest.php +++ b/tests/Transformers/CommentTransformerTest.php @@ -46,7 +46,7 @@ public function testWithoutOAuth(?string $groupIdentifier, bool $visible) } } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], diff --git a/tests/Transformers/DeclaredPermissionsTest.php b/tests/Transformers/DeclaredPermissionsTest.php index 3bb5cafd2dd..b6767dfd8ad 100644 --- a/tests/Transformers/DeclaredPermissionsTest.php +++ b/tests/Transformers/DeclaredPermissionsTest.php @@ -38,7 +38,7 @@ public function testPrivilegeExists($class, ?string $include, string $privilege) $this->assertTrue(method_exists(app('OsuAuthorize'), "check{$privilege}"), "{$class} uses check{$privilege} but is not implemented."); } - public function transformerClassesDataProvider() + public static function transformerClassesDataProvider() { return array_map( function ($class) { @@ -48,7 +48,7 @@ function ($class) { ); } - public function privilegeDataProvider() + public static function privilegeDataProvider() { $data = []; diff --git a/tests/Transformers/OAuth/ClientTransformerTest.php b/tests/Transformers/OAuth/ClientTransformerTest.php index e2bacff25e1..5e8d0912718 100644 --- a/tests/Transformers/OAuth/ClientTransformerTest.php +++ b/tests/Transformers/OAuth/ClientTransformerTest.php @@ -48,7 +48,7 @@ public function testRedirectAndSecretNotVisibleToGroup($groupIdentifier) $this->assertArrayNotHasKey('secret', $json); } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin'], diff --git a/tests/Transformers/PollOptionTransformerTest.php b/tests/Transformers/PollOptionTransformerTest.php index f4857104970..b7e9aa31cb4 100644 --- a/tests/Transformers/PollOptionTransformerTest.php +++ b/tests/Transformers/PollOptionTransformerTest.php @@ -64,7 +64,7 @@ public function testVoteCountPermissions( * - Authenticated user's group identifier * - Whether vote count should be visible */ - public function voteCountPermissionsDataProvider(): array + public static function voteCountPermissionsDataProvider(): array { return [ [true, true, true, 'admin', true], diff --git a/tests/Transformers/UserCompactTransformerTest.php b/tests/Transformers/UserCompactTransformerTest.php index f08c259bd2b..cc5bcd3b462 100644 --- a/tests/Transformers/UserCompactTransformerTest.php +++ b/tests/Transformers/UserCompactTransformerTest.php @@ -109,7 +109,7 @@ public function testPropertyIsVisibleWithoutOAuth(string $property) $this->assertArrayHasKey($property, $json); } - public function groupsDataProvider() + public static function groupsDataProvider() { return [ ['admin', true], @@ -120,7 +120,7 @@ public function groupsDataProvider() ]; } - public function propertyPermissionsDataProvider() + public static function propertyPermissionsDataProvider() { $data = []; $transformer = new UserTransformer(); diff --git a/tests/ZalgoTest.php b/tests/ZalgoTest.php index a03983954bb..09b54ac50ba 100644 --- a/tests/ZalgoTest.php +++ b/tests/ZalgoTest.php @@ -50,7 +50,7 @@ public function combinationExamples() ]; } - public function zalgoExamples() + public static function zalgoExamples() { return [ ['testing', 0], From 11e8d902cb083f2e3b60505c71c2f16279731124 Mon Sep 17 00:00:00 2001 From: Taevas <67872932+TTTaevas@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:10:19 +0100 Subject: [PATCH 27/33] Fix some typos in the API's documentation --- resources/views/docs/_using_chat.md | 6 +++--- resources/views/docs/_websocket_commands.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/docs/_using_chat.md b/resources/views/docs/_using_chat.md index 57a0bf82078..bd50c344efb 100644 --- a/resources/views/docs/_using_chat.md +++ b/resources/views/docs/_using_chat.md @@ -33,7 +33,7 @@ e.g. `GET` `/chat/updates?includes[]=presence` ## Creating a channel -Make a request to the [Create Channel](#create-channel) endpoint +Make a request to the [Create Channel](#create-channel) endpoint. Only `PM` and `ANNOUNCE` type channels may be created. Creating a channel will automatically join it. Re-creating a `PM` channel will simply rejoin the existing channel. @@ -42,7 +42,7 @@ Re-creating a `PM` channel will simply rejoin the existing channel. Make a request to the [Join Channel](#join-channel) endpoint where `channel` is the `channel_id`. -A [chat.channel.join](#chatchanneljoin) event is sent when the over the websocket when the user joins a channel. +A [chat.channel.join](#chatchanneljoin) event is sent over the websocket when the user joins a channel. ## Leaving a channel @@ -55,7 +55,7 @@ A [chat.channel.part](#chatchannelpart) event is sent over the websocket when th ## Sending messages Channels should be [joined](#joining-a-channel) or [created](#creating-a-channel) before messages are sent to them. -To send a message a channel, make a request to the [Send Message to Channel](#send-message-to-channel) endpoint. +To send a message to a channel, make a request to the [Send Message to Channel](#send-message-to-channel) endpoint. A [chat.message.new](#chatmessagenew) event is sent over the websocket when the user receives a message. diff --git a/resources/views/docs/_websocket_commands.md b/resources/views/docs/_websocket_commands.md index 701d2ffa8de..78eeb395f7d 100644 --- a/resources/views/docs/_websocket_commands.md +++ b/resources/views/docs/_websocket_commands.md @@ -27,5 +27,5 @@ webSocket.send(JSON.stringify({ event: 'chat.start' })); Send to the websocket to stop receiving chat messages. ```javascript -webSocket.send(JSON.stringify({ event: 'chat.start' })); +webSocket.send(JSON.stringify({ event: 'chat.end' })); ``` From abf1c323ba8bedc6c2b819f4be6d546d0ff80af3 Mon Sep 17 00:00:00 2001 From: bakaneko Date: Thu, 30 Nov 2023 13:07:14 +0900 Subject: [PATCH 28/33] don't renderer on same props, otherwise clicking also causes rerender --- resources/js/components/textarea-autosize.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/textarea-autosize.tsx b/resources/js/components/textarea-autosize.tsx index 4b120e5d856..244d7828a5c 100644 --- a/resources/js/components/textarea-autosize.tsx +++ b/resources/js/components/textarea-autosize.tsx @@ -14,7 +14,7 @@ interface State { lineHeight?: number; } -export default class TextareaAutosize extends React.Component { +export default class TextareaAutosize extends React.PureComponent { static readonly defaultProps = { async: false, rows: 1, From 437b1169a36e4e04cd0e4536822da78f78b5c980 Mon Sep 17 00:00:00 2001 From: bakaneko Date: Thu, 30 Nov 2023 14:44:39 +0900 Subject: [PATCH 29/33] update on componentDidUpdate but don't trigger double update on input. --- resources/js/components/textarea-autosize.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/resources/js/components/textarea-autosize.tsx b/resources/js/components/textarea-autosize.tsx index 244d7828a5c..8d1cd4afdf5 100644 --- a/resources/js/components/textarea-autosize.tsx +++ b/resources/js/components/textarea-autosize.tsx @@ -21,6 +21,7 @@ export default class TextareaAutosize extends React.PureComponent }; private readonly ref = this.props.innerRef ?? React.createRef(); + private shouldUpdate = true; private get maxHeight() { return this.props.maxRows != null && this.state.lineHeight != null @@ -61,6 +62,13 @@ export default class TextareaAutosize extends React.PureComponent if (this.ref.current.style.overflowX !== 'hidden') { this.ref.current.style.overflowX = 'hidden'; } + + // Avoid double updating since autosize automatically triggers update on input. + if (this.shouldUpdate) { + autosize.update(this.ref.current); + } else { + this.shouldUpdate = true; + } } componentWillUnmount() { @@ -69,16 +77,22 @@ export default class TextareaAutosize extends React.PureComponent } render() { - const { async, innerRef, maxRows, style, ...otherProps } = this.props; + const { async, innerRef, onInput, maxRows, style, ...otherProps } = this.props; const maxHeight = this.maxHeight; return (