From 736b65d0ca332ed72e98076661521548aece3bd9 Mon Sep 17 00:00:00 2001 From: Graham Aitken Date: Sat, 30 Mar 2024 19:00:52 +0000 Subject: [PATCH 1/4] Resolved DuskServiceProvider Larastan exceptions --- app/Providers/DuskServiceProvider.php | 40 +++++++++++++++++---------- phpstan.dist.neon | 1 - 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/Providers/DuskServiceProvider.php b/app/Providers/DuskServiceProvider.php index ff649e57..17e4e34b 100644 --- a/app/Providers/DuskServiceProvider.php +++ b/app/Providers/DuskServiceProvider.php @@ -6,49 +6,61 @@ use Illuminate\Support\ServiceProvider; use Laravel\Dusk\Browser; +/** + * @method string getScreenshotFilename(string $filename_base, string $count_string) + * @method Browser scrollToTop() + */ final class DuskServiceProvider extends ServiceProvider implements DeferrableProvider { /** Register Dusk's browser macros. */ public function boot(): void { - Browser::macro('getScreenshotFilename', function ($filename_base, $count) { + Browser::macro('getScreenshotFilename', function (string $filename_base, string $count_string) { return date('Y-m-d') . '_' . str_replace(' ', '_', strtolower(config('app.name'))) . '/' . config('dusk.screen_width') . 'x' . config('dusk.screen_height') - . '/' . $filename_base . '_' . $count; + . '/' . $filename_base . '_' . $count_string; }); - Browser::macro('screenshotWholePage', function ($filename_base) { - $this->scrollToTop()->pause(config('dusk.pause_length')); + Browser::macro('screenshotWholePage', function (string $filename_base) { + /** @var Browser $browser */ + $browser = $this; + $browser->scrollToTop()->pause(config('dusk.pause_length')); - $screen_max = ceil($this->script('return document.body.offsetHeight / window.innerHeight')[0]); + $screen_max = ceil($browser->script('return document.body.offsetHeight / window.innerHeight')[0]); for ($screen = 1; $screen <= $screen_max; $screen++) { - $this->screenshot($this->getScreenshotFilename($filename_base, $screen)) + $browser->screenshot($browser->getScreenshotFilename($filename_base, (string) $screen)) ->pause(config('dusk.pause_length')) ->scrollDownScreenHeight(); } - $this->scrollToTop()->pause(config('dusk.pause_length')); + $browser->scrollToTop()->pause(config('dusk.pause_length')); - return $this; + return $browser; }); Browser::macro('scrollToTop', function () { - $this->script('window.scrollTo(0, 0)'); + /** @var Browser $browser */ + $browser = $this; + $browser->script('window.scrollTo(0, 0)'); - return $this; + return $browser; }); Browser::macro('scrollDownScreenHeight', function () { - $this->script('window.scrollBy(0, window.innerHeight)'); + /** @var Browser $browser */ + $browser = $this; + $browser->script('window.scrollBy(0, window.innerHeight)'); - return $this; + return $browser; }); Browser::macro('scrollToEnd', function () { - $this->script('window.scrollTo(0, document.body.scrollHeight)'); + /** @var Browser $browser */ + $browser = $this; + $browser->script('window.scrollTo(0, document.body.scrollHeight)'); - return $this; + return $browser; }); } diff --git a/phpstan.dist.neon b/phpstan.dist.neon index a2bfcae4..6eff0161 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -20,6 +20,5 @@ parameters: - tests/Feature # 252 errors 2024-03-16 - tests/Pest # 32 errors 2024-03-16 - tests/Unit # 150 errors 2024-03-16 - - app/Providers/DuskServiceProvider.php # 8 errors 2024-02-01, all `Call to an undefined method App\Providers\DuskServiceProvider::` checkGenericClassInNonGenericObjectType: false From 1045b8d93ddb5e8ca8233fe84816e32d9d73526e Mon Sep 17 00:00:00 2001 From: Graham Aitken Date: Sat, 30 Mar 2024 19:07:29 +0000 Subject: [PATCH 2/4] Increased project PHPStan scope & noted remaining Lvl 8 issues --- phpstan.dist.neon | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 6eff0161..417c5f10 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -11,14 +11,29 @@ parameters: - tests/ # Level 9 is the highest level - level: 7 - - ignoreErrors: + level: 8 excludePaths: - - tests/Browser # 16 errors 2024-03-16, all `Call to an undefined method Laravel\Dusk\Browser::` - - tests/Feature # 252 errors 2024-03-16 - - tests/Pest # 32 errors 2024-03-16 - - tests/Unit # 150 errors 2024-03-16 + - tests/Pest/Functions/CompositeTests # 5 errors 2024-03-29, whole directory needs refactoring + + ignoreErrors: + - # From the way the methods are inherited by Pest + message: '#Call to an undefined method Pest\\#' + path: tests + - # To be fixed by adopting Pest syntax + message: '#Undefined variable: \$this#' + path: tests + - # From the way the methods are inherited by the Browser object + message: '#Call to an undefined method Laravel\\Dusk\\Browser::#' + path: tests/Browser + count: 16 + - # From the way the methods are inherited by Pest + message: '#Function testNotifiedUpdate#' + path: tests/Feature/Http/Controllers/Auth/PasswordForgottenControllerTest.php + count: 5 + - # From the way the methods are inherited by Pest + message: '#Access to an undefined property Pest\\Mixins\\Expectation\ Date: Sat, 30 Mar 2024 19:08:22 +0000 Subject: [PATCH 3/4] Fixes for newly exposed Larastan errors --- .../Auth/AuthenticatedSessionController.php | 2 +- .../Auth/EmailVerificationController.php | 12 ++++------- .../Auth/PasswordConfirmationController.php | 2 +- .../Controllers/Auth/ProfileController.php | 21 ++++++++++++++++--- .../Auth/ProfilePasswordController.php | 11 +++++++++- .../Controllers/Public/PageController.php | 12 +++++++++-- .../Views/Navbars/ItemLinksNavbarResource.php | 2 +- .../Sitemaps/HomepageSitemapResource.php | 4 +++- .../Views/Sitemaps/PageSitemapResource.php | 2 +- .../Auth/EmailVerificationControllerTest.php | 4 ++-- .../Auth/ProfileControllerTest.php | 3 +-- .../PasswordResetMetadataResourceTest.php | 3 +-- .../ProfileEditMetadataResourceTest.php | 3 +-- .../Sitemaps/HomepageSitemapResourceTest.php | 2 +- .../Sitemaps/PagesSitemapResourceTest.php | 2 +- 15 files changed, 56 insertions(+), 29 deletions(-) diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index baaad0bf..f74f4037 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -19,7 +19,7 @@ final class AuthenticatedSessionController extends Controller public function create(): RedirectResponse|Response { if (auth()->check()) { - return redirect(route('home')); + return to_route('dashboard'); } return (new AuthViewRepository)->getViewDetails( diff --git a/app/Http/Controllers/Auth/EmailVerificationController.php b/app/Http/Controllers/Auth/EmailVerificationController.php index 0d7543c8..658b9393 100644 --- a/app/Http/Controllers/Auth/EmailVerificationController.php +++ b/app/Http/Controllers/Auth/EmailVerificationController.php @@ -19,7 +19,7 @@ final class EmailVerificationController extends Controller /** Display the email verification prompt. */ public function show(): RedirectResponse|Response { - return request()->user()->hasVerifiedEmail() + return request()->user()?->hasVerifiedEmail() ? redirect()->intended(RouteServiceProvider::HOME) : (new AuthViewRepository)->getViewDetails( self::TEMPLATE_EMAIL_VERIFY, @@ -31,11 +31,11 @@ public function show(): RedirectResponse|Response /** Send a new email verification notification, after a manual request by the user. */ public function store(): RedirectResponse { - if (request()->user()->hasVerifiedEmail()) { + if (request()->user()?->hasVerifiedEmail()) { return redirect()->intended(RouteServiceProvider::HOME); } - request()->user()->sendEmailVerificationNotification(); + request()->user()?->sendEmailVerificationNotification(); return back()->with('status', 'verification-link-sent'); } @@ -43,11 +43,7 @@ public function store(): RedirectResponse /** Mark the authenticated user's email address as verified. */ public function edit(EmailVerificationRequest $request): RedirectResponse { - if ($request->user()->hasVerifiedEmail()) { - return redirect()->intended(RouteServiceProvider::HOME . '?verified=1'); - } - - if ($request->user()->markEmailAsVerified()) { + if (!$request->user()?->hasVerifiedEmail() && $request->user()?->markEmailAsVerified()) { /** @var MustVerifyEmail $user */ $user = $request->user(); event(new Verified($user)); diff --git a/app/Http/Controllers/Auth/PasswordConfirmationController.php b/app/Http/Controllers/Auth/PasswordConfirmationController.php index 3399a972..51b6f76b 100644 --- a/app/Http/Controllers/Auth/PasswordConfirmationController.php +++ b/app/Http/Controllers/Auth/PasswordConfirmationController.php @@ -30,7 +30,7 @@ public function show(): Response public function store(Request $request): RedirectResponse { if (!Auth::guard('web')->validate([ - 'email' => $request->user()->email, + 'email' => $request->user()?->email, 'password' => $request->password, ])) { throw ValidationException::withMessages([ diff --git a/app/Http/Controllers/Auth/ProfileController.php b/app/Http/Controllers/Auth/ProfileController.php index c7424132..e689c1e6 100644 --- a/app/Http/Controllers/Auth/ProfileController.php +++ b/app/Http/Controllers/Auth/ProfileController.php @@ -16,6 +16,7 @@ use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Inertia\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; final class ProfileController extends Controller { @@ -71,9 +72,17 @@ public function edit(): Response ); } - /** Update the authenticated user's profile information. */ + /** + * Update the authenticated user's profile information. + * + * @throws HttpException + */ public function update(ProfileUpdateRequest $request): RedirectResponse { + if (!$request->user()) { + abort(401); + } + $request->user()->fill($request->validated()); if ($request->user()->isDirty('email')) { @@ -85,10 +94,16 @@ public function update(ProfileUpdateRequest $request): RedirectResponse return to_route('profile.edit'); } - /** Delete the authenticated user's account. */ + /** + * Delete the authenticated user's account. + * + * @throws HttpException + */ public function destroy(ProfileDeleteRequest $request): RedirectResponse { - $user = $request->user(); + if (!$user = $request->user()) { + abort(401); + } auth()->logout(); diff --git a/app/Http/Controllers/Auth/ProfilePasswordController.php b/app/Http/Controllers/Auth/ProfilePasswordController.php index 9da3b506..cba68591 100644 --- a/app/Http/Controllers/Auth/ProfilePasswordController.php +++ b/app/Http/Controllers/Auth/ProfilePasswordController.php @@ -5,12 +5,21 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Auth\PasswordUpdateRequest; use Illuminate\Http\RedirectResponse; +use Symfony\Component\HttpKernel\Exception\HttpException; final class ProfilePasswordController extends Controller { - /** Update the authenticated user's password. */ + /** + * Update the authenticated user's password. + * + * @throws HttpException + */ public function update(PasswordUpdateRequest $request): RedirectResponse { + if (!$request->user()) { + abort(401); + } + $inputs = $request->validated(); $request->user()->update([ diff --git a/app/Http/Controllers/Public/PageController.php b/app/Http/Controllers/Public/PageController.php index 15b33334..eda4fcf6 100644 --- a/app/Http/Controllers/Public/PageController.php +++ b/app/Http/Controllers/Public/PageController.php @@ -15,9 +15,17 @@ final class PageController extends Controller { - /** Display the given page. */ + /** + * Display the given page. + * + * @throws HttpException + */ public function show(Page $page): Response { + if (!$page->template) { + abort(404); + } + return (new PublicViewRepository) ->getViewDetails( $page->template, @@ -59,7 +67,7 @@ public function store(Request $request, Page $page): RedirectResponse $files = [$files]; } - if (!$generated_filename = $files[0]->store('uploads/pdf')) { + if (!$generated_filename = $files[0]?->store('uploads/pdf')) { Log::error('ERROR | Failed to store uploaded PDF file for request:', $response); abort(500, 'Failed to store uploaded PDF file'); diff --git a/app/Http/Resources/Views/Navbars/ItemLinksNavbarResource.php b/app/Http/Resources/Views/Navbars/ItemLinksNavbarResource.php index b97aee57..565f7f8f 100644 --- a/app/Http/Resources/Views/Navbars/ItemLinksNavbarResource.php +++ b/app/Http/Resources/Views/Navbars/ItemLinksNavbarResource.php @@ -10,7 +10,7 @@ final class ItemLinksNavbarResource implements NavbarItemInterface /** * Get the content array for the given page's public link. * - * @return array> + * @return array> */ public function getItem(NavbarItem $navbar_item): array { diff --git a/app/Http/Resources/Views/Sitemaps/HomepageSitemapResource.php b/app/Http/Resources/Views/Sitemaps/HomepageSitemapResource.php index 8bd12f4d..d4d7fd12 100644 --- a/app/Http/Resources/Views/Sitemaps/HomepageSitemapResource.php +++ b/app/Http/Resources/Views/Sitemaps/HomepageSitemapResource.php @@ -14,9 +14,11 @@ final class HomepageSitemapResource implements ConstantItemInterface */ public function getItem(): array { + $lastmod = strtotime(strval(Page::max('updated_at'))); + return [ 'loc' => route('home'), - 'lastmod' => date('Y-m-d', strtotime(Page::max('updated_at'))), + 'lastmod' => $lastmod ? date('Y-m-d', $lastmod) : '2024-03-01', 'changefreq' => 'weekly', 'priority' => 0.8, ]; diff --git a/app/Http/Resources/Views/Sitemaps/PageSitemapResource.php b/app/Http/Resources/Views/Sitemaps/PageSitemapResource.php index de2355c1..7eb0c46f 100644 --- a/app/Http/Resources/Views/Sitemaps/PageSitemapResource.php +++ b/app/Http/Resources/Views/Sitemaps/PageSitemapResource.php @@ -14,7 +14,7 @@ final class PageSitemapResource implements PageItemInterface */ public function getItem(Page $page): array { - $updated_at = strtotime($page->updated_at); + $updated_at = strtotime(strval($page->updated_at)); $lastmod = $updated_at ? date('Y-m-d', $updated_at) : strval(config('metadata.first_published_year')) . '-01-01'; return [ diff --git a/tests/Feature/Http/Controllers/Auth/EmailVerificationControllerTest.php b/tests/Feature/Http/Controllers/Auth/EmailVerificationControllerTest.php index 8f962f11..8148b446 100644 --- a/tests/Feature/Http/Controllers/Auth/EmailVerificationControllerTest.php +++ b/tests/Feature/Http/Controllers/Auth/EmailVerificationControllerTest.php @@ -80,7 +80,7 @@ $actual = $this->actingAs($user)->get($verificationUrl); Event::assertDispatched(Verified::class); - expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); + expect($user->fresh()?->hasVerifiedEmail())->toBeTrue(); $actual ->assertSessionHasNoErrors() @@ -96,5 +96,5 @@ $this->actingAs($user)->get($verificationUrl); - expect($user->fresh()->hasVerifiedEmail())->toBeFalse(); + expect($user->fresh()?->hasVerifiedEmail())->toBeFalse(); })->with('users-unverified'); diff --git a/tests/Feature/Http/Controllers/Auth/ProfileControllerTest.php b/tests/Feature/Http/Controllers/Auth/ProfileControllerTest.php index 21de30f9..b64ef806 100644 --- a/tests/Feature/Http/Controllers/Auth/ProfileControllerTest.php +++ b/tests/Feature/Http/Controllers/Auth/ProfileControllerTest.php @@ -6,7 +6,6 @@ use App\Http\Resources\Views\Auth\Metadata\DashboardMetadataResource; use App\Http\Resources\Views\Auth\Metadata\ProfileEditMetadataResource; use App\Models\User; -use Illuminate\Http\Request; test('TEMPLATE_DASHBOARD Vue page component exists', function () { $template = (new ReflectionClassConstant( @@ -76,7 +75,7 @@ ->toHaveCorrectPropsAuth( ProfileController::TEMPLATE_PROFILE_EDIT, [], - (new ProfileEditMetadataResource)->getItem(new Request) + (new ProfileEditMetadataResource)->getItem() ); })->with('users'); diff --git a/tests/Unit/App/Http/Resources/Views/Auth/Metadata/PasswordResetMetadataResourceTest.php b/tests/Unit/App/Http/Resources/Views/Auth/Metadata/PasswordResetMetadataResourceTest.php index dd827315..cf143efe 100644 --- a/tests/Unit/App/Http/Resources/Views/Auth/Metadata/PasswordResetMetadataResourceTest.php +++ b/tests/Unit/App/Http/Resources/Views/Auth/Metadata/PasswordResetMetadataResourceTest.php @@ -2,14 +2,13 @@ use App\Http\Resources\Views\Auth\Metadata\PasswordResetMetadataResource; use App\Interfaces\Resources\Items\ConstantItemInterface; -use Illuminate\Http\Request; arch('it implements the expected interface') ->expect(PasswordResetMetadataResource::class) ->toImplement(ConstantItemInterface::class); test('getItem returns ok', function () { - $actual = (new PasswordResetMetadataResource)->getItem(new Request); + $actual = (new PasswordResetMetadataResource)->getItem(); expect($actual) ->toHaveCamelCaseKeys() diff --git a/tests/Unit/App/Http/Resources/Views/Auth/Metadata/ProfileEditMetadataResourceTest.php b/tests/Unit/App/Http/Resources/Views/Auth/Metadata/ProfileEditMetadataResourceTest.php index d15c2301..b4dd4f80 100644 --- a/tests/Unit/App/Http/Resources/Views/Auth/Metadata/ProfileEditMetadataResourceTest.php +++ b/tests/Unit/App/Http/Resources/Views/Auth/Metadata/ProfileEditMetadataResourceTest.php @@ -2,14 +2,13 @@ use App\Http\Resources\Views\Auth\Metadata\ProfileEditMetadataResource; use App\Interfaces\Resources\Items\ConstantItemInterface; -use Illuminate\Http\Request; arch('it implements the expected interface') ->expect(ProfileEditMetadataResource::class) ->toImplement(ConstantItemInterface::class); test('getItem returns ok', function () { - $actual = (new ProfileEditMetadataResource)->getItem(new Request); + $actual = (new ProfileEditMetadataResource)->getItem(); expect($actual) ->toHaveCamelCaseKeys() diff --git a/tests/Unit/App/Http/Resources/Views/Sitemaps/HomepageSitemapResourceTest.php b/tests/Unit/App/Http/Resources/Views/Sitemaps/HomepageSitemapResourceTest.php index 1501ad8c..5005e4f8 100644 --- a/tests/Unit/App/Http/Resources/Views/Sitemaps/HomepageSitemapResourceTest.php +++ b/tests/Unit/App/Http/Resources/Views/Sitemaps/HomepageSitemapResourceTest.php @@ -22,7 +22,7 @@ ->toHaveCount(4) ->toMatchArray([ 'loc' => url('/'), - 'lastmod' => '1970-01-01', + 'lastmod' => '2024-03-01', 'changefreq' => 'weekly', 'priority' => 0.8, ]); diff --git a/tests/Unit/App/Http/Resources/Views/Sitemaps/PagesSitemapResourceTest.php b/tests/Unit/App/Http/Resources/Views/Sitemaps/PagesSitemapResourceTest.php index 18cba556..2a4cdac0 100644 --- a/tests/Unit/App/Http/Resources/Views/Sitemaps/PagesSitemapResourceTest.php +++ b/tests/Unit/App/Http/Resources/Views/Sitemaps/PagesSitemapResourceTest.php @@ -26,7 +26,7 @@ ->toHaveCount(4) ->toMatchArray([ 'loc' => url('/'), - 'lastmod' => '1970-01-01', + 'lastmod' => '2024-03-01', 'changefreq' => 'weekly', 'priority' => 0.8, ]); From bce60ff7bb9fa5e5df73797d398b8d25fa7a3d47 Mon Sep 17 00:00:00 2001 From: Graham Aitken Date: Sat, 30 Mar 2024 19:08:41 +0000 Subject: [PATCH 4/4] Tidied up DocBlocks --- app/Models/Page.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Models/Page.php b/app/Models/Page.php index 0ab5e434..0b5481c9 100644 --- a/app/Models/Page.php +++ b/app/Models/Page.php @@ -49,17 +49,13 @@ final class Page extends Model 'is_homepage' => 'boolean', ]; - /** GETTERS */ - /** Get the HTML content string for the page. */ public function getContent(): string { return $this->content ?? ''; } - /** - * Get the public URL's path for the page. - */ + /** Get the public URL's path for the page. */ public function getPath(): string { return $this->is_homepage @@ -81,8 +77,6 @@ public function getUrl(): string : route('page.show', $this->slug); } - /** SCOPES */ - /** Returns all Page models that should be in the sitemap (in_sitemap = 1). */ public function scopeInSitemap(Builder|QueryBuilder $query): Builder|QueryBuilder {