create(['portal' => $portal, 'parent_id' => $parentId]); $category->translations()->create([ 'locale' => 'de', 'name' => $name, 'slug' => $slug, ]); return $category; } test('homepage data is cached per portal and language', function () { /** @var TestCase $this */ $category = makeCategoryWithSlug(Portal::Presseecho, 'Energie & Klima', 'energie-klima'); $company = Company::factory()->presseecho()->create(); PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Erste Meldung im Cache', 'slug' => 'erste-meldung-im-cache', 'published_at' => now()->subHour(), ]); $this->get('https://presseecho.test/de') ->assertSuccessful() ->assertSeeText('Erste Meldung im Cache'); expect(Cache::has('web.home.presseecho.de'))->toBeTrue(); PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Neue Meldung nach Cache-Aufbau', 'slug' => 'neue-meldung-nach-cache-aufbau', 'published_at' => now()->subMinutes(5), ]); // Innerhalb der TTL liefert HomePageData den gecachten Stand: Lead bleibt // die alte Meldung. (Der Livewire-Feed ist bewusst ungecacht und zeigt // neue Meldungen sofort.) $cachedLead = $this->get('https://presseecho.test/de') ->assertSuccessful() ->viewData('leadRelease'); expect($cachedLead->title)->toBe('Erste Meldung im Cache'); Cache::flush(); $freshLead = $this->get('https://presseecho.test/de') ->assertSuccessful() ->viewData('leadRelease'); expect($freshLead->title)->toBe('Neue Meldung nach Cache-Aufbau'); }); test('category page data is cached per portal, language and slug but 404s are not cached', function () { /** @var TestCase $this */ $category = makeCategoryWithSlug(Portal::Businessportal24, 'Mobilität', 'mobilitaet'); PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->for($category) ->for(Company::factory()->businessportal24()) ->create([ 'slug' => 'mobilitaets-meldung', 'published_at' => now()->subDay(), ]); $this->get('https://businessportal24.test/de/category/mobilitaet')->assertSuccessful(); expect(Cache::has('web.category.businessportal24.de.mobilitaet'))->toBeTrue(); $this->get('https://businessportal24.test/de/category/gibt-es-nicht')->assertNotFound(); expect(Cache::has('web.category.businessportal24.de.gibt-es-nicht'))->toBeFalse(); }); test('search sidebar data is cached per portal and language', function () { /** @var TestCase $this */ $this->get('https://businessportal24.test/de/suche')->assertSuccessful(); expect(Cache::has('web.search.most-read.businessportal24-both.de'))->toBeTrue(); }); test('newsroom detail data is cached per portal language slug and page', function () { /** @var TestCase $this */ $company = Company::factory()->businessportal24()->create([ 'slug' => 'cache-newsroom', 'is_active' => true, ]); PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->create([ 'company_id' => $company->id, 'language' => 'de', 'published_at' => now()->subDay(), ]); $this->get('https://businessportal24.test/de/newsroom/cache-newsroom?page=2') ->assertSuccessful(); expect(Cache::has('web.newsroom.businessportal24-both.de.cache-newsroom.page.2'))->toBeTrue(); }); test('newsroom detail cache key aligns with the paginator so an invalid page cannot poison another page', function () { /** @var TestCase $this */ $company = Company::factory()->businessportal24()->create([ 'slug' => 'poison-newsroom', 'is_active' => true, ]); PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->create([ 'company_id' => $company->id, 'language' => 'de', 'published_at' => now()->subDay(), ]); // Ungültige Seitennummer: der Paginator rendert Seite 1, also darf der // Inhalt NICHT unter dem Key für Seite 2 landen (sonst Cache-Poisoning). $this->get('https://businessportal24.test/de/newsroom/poison-newsroom?page=2.5') ->assertSuccessful(); expect(Cache::has('web.newsroom.businessportal24-both.de.poison-newsroom.page.1'))->toBeTrue(); expect(Cache::has('web.newsroom.businessportal24-both.de.poison-newsroom.page.2'))->toBeFalse(); }); test('newsrooms unfiltered index is cached but filtered searches are not', function () { /** @var TestCase $this */ $company = Company::factory()->businessportal24()->create([ 'name' => 'SolarTec Bestand', 'is_active' => true, ]); PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->for($company) ->create(['language' => 'de', 'published_at' => now()->subDay()]); // Ungefiltertes Verzeichnis (erste Seite) wird unter einem beschränkten Key gecacht. $this->get('https://businessportal24.test/de/newsrooms') ->assertSuccessful() ->assertSeeText('SolarTec Bestand'); expect(Cache::has('web.newsrooms.businessportal24,both.de.page.1'))->toBeTrue(); // Gefilterte Suche wird NICHT gecacht (Schutz vor unbegrenztem Key-Raum): // eine nach dem ersten Aufruf angelegte Firma erscheint sofort. $newCompany = Company::factory()->businessportal24()->create([ 'name' => 'SolarTec Neu', 'is_active' => true, ]); PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->for($newCompany) ->create(['language' => 'de', 'published_at' => now()->subHour()]); $this->get('https://businessportal24.test/de/newsrooms?q=solartec') ->assertSuccessful() ->assertSeeText('SolarTec Neu'); $this->get('https://businessportal24.test/de/newsrooms?q=solartec') ->assertSuccessful() ->assertSeeText('SolarTec Neu'); }); test('deep newsrooms pages are not cached', function () { /** @var TestCase $this */ $this->get('https://businessportal24.test/de/newsrooms?page=25')->assertSuccessful(); expect(Cache::has('web.newsrooms.businessportal24,both.de.page.25'))->toBeFalse(); }); test('newsrooms cache does not retain unrelated query parameters in pagination links', function () { /** @var TestCase $this */ $response = $this->get('https://businessportal24.test/de/newsrooms?q=SolarTec&utm_source=poison') ->assertSuccessful(); $pageTwoUrl = $response->viewData('newsrooms')->url(2); expect($pageTwoUrl) ->toContain('q=SolarTec') ->not->toContain('utm_source'); }); test('subcategory counts are aggregated correctly in a single grouped query', function () { /** @var TestCase $this */ $parent = makeCategoryWithSlug(Portal::Both, 'Wirtschaft', 'wirtschaft'); $child = makeCategoryWithSlug(Portal::Both, 'Handel', 'handel', $parent->id); $company = Company::factory()->businessportal24()->create(); // Stichtag (referenceNow) = jüngste Meldung: 2 im 7-Tage-Fenster, 1 älter (>14 Tage). foreach ([1, 2, 20] as $daysAgo) { PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->for($child) ->for($company) ->create([ 'slug' => "handel-meldung-{$daysAgo}", 'published_at' => now()->subDays($daysAgo), ]); } $response = $this->get('https://businessportal24.test/de/category/wirtschaft')->assertSuccessful(); $subCategories = $response->viewData('subCategories'); expect($subCategories)->toHaveCount(1) ->and($subCategories[0]['name'])->toBe('Handel') ->and($subCategories[0]['total'])->toBe(3) ->and($subCategories[0]['recent'])->toBe(2) ->and($subCategories[0]['delta'])->toBe(2); }); test('the press release feed today filter matches the reference day via range comparison', function () { /** @var TestCase $this */ $category = makeCategoryWithSlug(Portal::Presseecho, 'Technologie', 'technologie'); $company = Company::factory()->presseecho()->create(); // Historisches Archiv: Stichtag = jüngste Meldung (vor 10 Tagen). PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Meldung am Stichtag', 'slug' => 'meldung-am-stichtag', 'published_at' => now()->subDays(10)->setTime(9, 0), ]); PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Meldung vom Vortag', 'slug' => 'meldung-vom-vortag', 'published_at' => now()->subDays(11)->setTime(9, 0), ]); LivewireVolt::test('web.press-release-feed', ['portal' => Portal::Presseecho->value, 'language' => 'de']) ->call('setTimeframe', 'today') ->assertSee('Meldung am Stichtag') ->assertDontSee('Meldung vom Vortag'); });