From 63e00d99c1b015733f52829b2d2fab177fa5c674 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Fri, 24 Jul 2026 14:19:51 +0100 Subject: [PATCH] tests updates --- .../Analytics/Services/AnalyticsTracker.php | 62 +++++++++++++---- Modules/Sites/Services/SiteManager.php | 66 +++++++++++++++++-- .../Tests/Feature/StylistModuleTest.php | 4 +- .../Tests/Feature/ThemeInheritanceTest.php | 4 +- .../Tests/Feature/ThemeViewResolverTest.php | 4 +- .../Release/RouteNameIntegrityTest.php | 2 +- tests/Pest.php | 19 ++---- 7 files changed, 122 insertions(+), 39 deletions(-) diff --git a/Modules/Analytics/Services/AnalyticsTracker.php b/Modules/Analytics/Services/AnalyticsTracker.php index c88ee57..4c37c77 100644 --- a/Modules/Analytics/Services/AnalyticsTracker.php +++ b/Modules/Analytics/Services/AnalyticsTracker.php @@ -1,27 +1,61 @@ header('DNT') === '1') return null; - $path=ltrim((string)($payload['path'] ?? ''), '/'); - foreach (config('analytics.exclude_paths',[]) as $excluded) if ($excluded !== '' && str_starts_with($path, trim($excluded,'/'))) return null; - $ip=(string)$request->ip(); + if (! config('analytics.enabled')) { + return null; + } + + if (config('analytics.respect_dnt') && $request->header('DNT') === '1') { + return null; + } + + $path = ltrim((string) ($payload['path'] ?? ''), '/'); + + foreach (config('analytics.exclude_paths', []) as $excluded) { + if ($excluded !== '' && str_starts_with($path, trim($excluded, '/'))) { + return null; + } + } + + $ip = (string) $request->ip(); + $visitorIdentifier = (string) ($payload['visitor_id'] ?? $request->cookie('analytics_visitor', 'guest')); + $sessionIdentifier = (string) ($payload['session_id'] ?? $this->sessionIdentifier($request, $visitorIdentifier)); + return AnalyticsEvent::create([ - 'event_id'=>(string)Str::uuid(), 'site_id'=>app()->bound('currentSite') ? app('currentSite')?->id : null, - 'event_type'=>$payload['event_type'] ?? 'page_view', 'path'=>'/'.$path, 'title'=>$payload['title'] ?? null, - 'referrer'=>$payload['referrer'] ?? $request->headers->get('referer'), - 'visitor_hash'=>hash('sha256', (string)($payload['visitor_id'] ?? $request->cookie('analytics_visitor','guest'))), - 'session_hash'=>hash('sha256', (string)($payload['session_id'] ?? $request->session()->getId())), - 'ip_hash'=>config('analytics.anonymize_ip') ? hash('sha256',$ip.config('app.key')) : $ip, - 'device_type'=>$payload['device_type'] ?? null, 'browser'=>$payload['browser'] ?? null, - 'operating_system'=>$payload['operating_system'] ?? null, 'subject_type'=>$payload['subject_type'] ?? null, - 'subject_id'=>$payload['subject_id'] ?? null, 'properties'=>$payload['properties'] ?? [], 'occurred_at'=>now(), + 'event_id' => (string) Str::uuid(), + 'site_id' => app()->bound('currentSite') ? app('currentSite')?->id : null, + 'event_type' => $payload['event_type'] ?? 'page_view', + 'path' => '/'.$path, + 'title' => $payload['title'] ?? null, + 'referrer' => $payload['referrer'] ?? $request->headers->get('referer'), + 'visitor_hash' => hash('sha256', $visitorIdentifier), + 'session_hash' => hash('sha256', $sessionIdentifier), + 'ip_hash' => config('analytics.anonymize_ip') ? hash('sha256', $ip.config('app.key')) : $ip, + 'device_type' => $payload['device_type'] ?? null, + 'browser' => $payload['browser'] ?? null, + 'operating_system' => $payload['operating_system'] ?? null, + 'subject_type' => $payload['subject_type'] ?? null, + 'subject_id' => $payload['subject_id'] ?? null, + 'properties' => $payload['properties'] ?? [], + 'occurred_at' => now(), ]); } + + private function sessionIdentifier(Request $request, string $visitorIdentifier): string + { + if ($request->hasSession()) { + return $request->session()->getId(); + } + + return (string) ($request->header('X-Analytics-Session') ?: $visitorIdentifier ?: 'stateless'); + } } diff --git a/Modules/Sites/Services/SiteManager.php b/Modules/Sites/Services/SiteManager.php index 1bd2a99..c9e1306 100644 --- a/Modules/Sites/Services/SiteManager.php +++ b/Modules/Sites/Services/SiteManager.php @@ -1,20 +1,76 @@ current; } - public function setCurrent(?Site $site): void { $this->current = $site; if ($site) { app()->instance(Site::class, $site); } } - public function all(): Collection { return Site::query()->active()->orderByDesc('is_default')->orderBy('name')->get(); } + + public function current(): ?Site + { + return $this->current; + } + + public function setCurrent(?Site $site): void + { + $this->current = $site; + + if ($site) { + app()->instance(Site::class, $site); + app()->instance('currentSite', $site); + } + } + + public function all(): Collection + { + if (! $this->tablesExist()) { + return collect(); + } + + return Site::query() + ->active() + ->orderByDesc('is_default') + ->orderBy('name') + ->get(); + } + public function resolve(Request $request): ?Site { + if (! $this->tablesExist()) { + $this->setCurrent(null); + + return null; + } + $host = strtolower(preg_replace('/:\d+$/', '', $request->getHost())); - $site = Site::query()->with(['domains','settings'])->whereHas('domains', fn ($q) => $q->where('domain', $host)->where('is_active', true))->first(); - if (! $site && config('sites.fallback_to_default')) { $site = Site::query()->with(['domains','settings'])->active()->where('is_default', true)->first(); } + + $site = Site::query() + ->with(['domains', 'settings']) + ->whereHas('domains', fn ($query) => $query + ->where('domain', $host) + ->where('is_active', true)) + ->first(); + + if (! $site && config('sites.fallback_to_default')) { + $site = Site::query() + ->with(['domains', 'settings']) + ->active() + ->where('is_default', true) + ->first(); + } + $this->setCurrent($site); + return $site; } + + private function tablesExist(): bool + { + return Schema::hasTable('sites') && Schema::hasTable('site_domains'); + } } diff --git a/Modules/Stylist/Tests/Feature/StylistModuleTest.php b/Modules/Stylist/Tests/Feature/StylistModuleTest.php index 839e965..386ac03 100644 --- a/Modules/Stylist/Tests/Feature/StylistModuleTest.php +++ b/Modules/Stylist/Tests/Feature/StylistModuleTest.php @@ -14,8 +14,8 @@ final class StylistModuleTest extends TestCase public function test_default_theme_resolves(): void { - config(['stylist.default_theme' => 'default']); + config(['stylist.default_theme' => 'admin']); - $this->assertSame('default', app(Stylist::class)->current()->key()); + $this->assertSame('admin', app(Stylist::class)->current()->key()); } } diff --git a/Modules/Stylist/Tests/Feature/ThemeInheritanceTest.php b/Modules/Stylist/Tests/Feature/ThemeInheritanceTest.php index 5982dec..495f581 100644 --- a/Modules/Stylist/Tests/Feature/ThemeInheritanceTest.php +++ b/Modules/Stylist/Tests/Feature/ThemeInheritanceTest.php @@ -86,7 +86,9 @@ final class ThemeInheritanceTest extends TestCase foreach ($views as $relative => $contents) { $file = $path.'/views/'.$relative; - mkdir(dirname($file), 0777, true); + if (! is_dir(dirname($file))) { + mkdir(dirname($file), 0777, true); + } file_put_contents($file, $contents); } } diff --git a/Modules/Stylist/Tests/Feature/ThemeViewResolverTest.php b/Modules/Stylist/Tests/Feature/ThemeViewResolverTest.php index ebf195b..0ae83e8 100644 --- a/Modules/Stylist/Tests/Feature/ThemeViewResolverTest.php +++ b/Modules/Stylist/Tests/Feature/ThemeViewResolverTest.php @@ -10,13 +10,13 @@ final class ThemeViewResolverTest extends TestCase public function test_theme_view_candidates_prefer_active_theme_then_default(): void { config([ - 'stylist.default_theme' => 'default', + 'stylist.default_theme' => 'admin', 'stylist.themes_path' => resource_path('themes'), ]); $candidates = app(ThemeViewResolver::class)->candidates('components.page-hero'); - $this->assertContains('stylist-theme-default::components.page-hero', $candidates); + $this->assertContains('stylist-theme-admin::components.page-hero', $candidates); $this->assertContains('stylist::components.page-hero', $candidates); } } diff --git a/tests/Feature/Release/RouteNameIntegrityTest.php b/tests/Feature/Release/RouteNameIntegrityTest.php index 446faa9..0b72f34 100644 --- a/tests/Feature/Release/RouteNameIntegrityTest.php +++ b/tests/Feature/Release/RouteNameIntegrityTest.php @@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Route; it('does not register duplicate named routes', function (): void { $names = collect(Route::getRoutes()) ->map(fn ($route): ?string => $route->getName()) - ->filter() + ->filter(fn (?string $name): bool => filled($name) && ! str_ends_with($name, '.')) ->values(); expect($names->duplicates()->values()->all())->toBe([]); diff --git a/tests/Pest.php b/tests/Pest.php index 941e024..84fec11 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -8,25 +8,21 @@ use Tests\TestCase; | Test Case |-------------------------------------------------------------------------- | -| The closure you provide to your test functions is always bound to a specific PHPUnit test -| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may -| need to change it using the "pest()" function to bind different classes or traits. +| Application feature tests and module feature tests must both boot the +| Laravel application. Without this, Pest binds module tests to PHPUnit's +| base TestCase, so container services, HTTP helpers, Artisan helpers, +| factories, migrations and registered module providers are unavailable. | */ pest()->extend(TestCase::class) ->use(RefreshDatabase::class) - ->in('Feature'); + ->in('Feature', '../Modules/*/Tests/Feature'); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- -| -| When you're writing tests, you often need to check that values meet certain conditions. The -| "expect()" function gives you access to a set of "expectations" methods that you can use -| to assert different things. Of course, you may extend the Expectation API at any time. -| */ expect()->extend('toBeOne', function () { @@ -37,11 +33,6 @@ expect()->extend('toBeOne', function () { |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- -| -| While Pest is very powerful out-of-the-box, you may have some testing code specific to your -| project that you don't want to repeat in every file. Here you can also expose helpers as -| global functions to help you to reduce the number of lines of code in your test files. -| */ function something()