create(['portal' => $portal, 'is_active' => true, 'parent_id' => $parent?->id]); $category->translations()->create(['locale' => 'de', 'name' => $name, 'slug' => $slug, 'description' => 'Meldungen aus dem Bereich '.$name.'.']); $category->translations()->create(['locale' => 'en', 'name' => $name.' EN', 'slug' => $slug.'-en']); return $category; } function feedRelease(Category $category, string $slug, array $attributes = [], Portal $portal = Portal::Presseecho): PressRelease { return PressRelease::factory() ->published() ->forPortal($portal) ->for($category) ->for(Company::factory()->presseecho()->create(['name' => 'Feed GmbH & Co.'])) ->create(['slug' => $slug, 'published_at' => now()->subHour(), 'language' => 'de', ...$attributes]); } test('the category feed lists the newest releases of the category and its children on the own portal', function () { /** @var TestCase $this */ $category = feedCategory(); $child = feedCategory(Portal::Presseecho, 'einzelhandel', 'Einzelhandel', $category); $other = feedCategory(Portal::Presseecho, 'energie', 'Energie'); feedRelease($category, 'neu', ['title' => 'Neue Meldung & mehr', 'text' => '

Erster Absatz der Meldung.

', 'published_at' => now()->subHour()]); feedRelease($child, 'kind', ['title' => 'Aus der Unterkategorie', 'published_at' => now()->subHours(2)]); feedRelease($other, 'andere-branche'); feedRelease($category, 'fremdes-portal', [], Portal::Businessportal24); feedRelease($category, 'terminiert', ['published_at' => now()->addHour()]); feedRelease($category, 'englisch', ['language' => 'en']); feedRelease($category, 'beide-portale', ['published_at' => now()->subHours(3)], Portal::Both); $response = $this->get('https://presseecho.test/de/category/handel/feed')->assertSuccessful(); expect($response->headers->get('Content-Type'))->toBe('application/rss+xml; charset=UTF-8'); $xml = (string) $response->getContent(); expect($xml) ->toStartWith('') ->toContain('Pressemitteilungen Handel – presseecho') ->toContain('https://presseecho.test/de/category/handel') ->toContain('') ->toContain('Meldungen aus dem Bereich Handel.') ->toContain('de') ->toContain('Neue Meldung & mehr') ->toContain('https://presseecho.test/de/press-release/neu') ->toContain('Erster Absatz der Meldung.') ->toContain('Handel') ->toContain('Feed GmbH & Co.') ->toContain('https://presseecho.test/de/press-release/kind') ->toContain('https://presseecho.test/de/press-release/beide-portale'); // Neueste zuerst. expect(strpos($xml, 'press-release/neu'))->toBeLessThan((int) strpos($xml, 'press-release/kind')); foreach (['andere-branche', 'fremdes-portal', 'terminiert', 'press-release/englisch'] as $absent) { $this->assertStringNotContainsString($absent, $xml); } // Wohlgeformt: die Ausgabe muss ein XML-Parser lesen können. $document = simplexml_load_string($xml); expect($document)->not->toBeFalse() ->and(count($document->channel->item))->toBe(3); }); test('the portal feed lists releases of all categories, newest first, capped', function () { /** @var TestCase $this */ $handel = feedCategory(); $energie = feedCategory(Portal::Presseecho, 'energie', 'Energie'); feedRelease($handel, 'handel-neu', ['published_at' => now()->subHour()]); feedRelease($energie, 'energie-alt', ['published_at' => now()->subDay()]); feedRelease($handel, 'fremd', [], Portal::Businessportal24); $xml = (string) $this->get('https://presseecho.test/de/feed')->assertSuccessful()->getContent(); expect($xml) ->toContain('Pressemitteilungen – presseecho') ->toContain('https://presseecho.test/de') ->toContain('') ->toContain('press-release/handel-neu') ->toContain('press-release/energie-alt'); expect(strpos($xml, 'handel-neu'))->toBeLessThan((int) strpos($xml, 'energie-alt')); $this->assertStringNotContainsString('press-release/fremd', $xml); }); test('an unknown or inactive category has no feed', function () { /** @var TestCase $this */ $inactive = feedCategory(Portal::Presseecho, 'veraltet', 'Veraltet'); $inactive->update(['is_active' => false]); $this->get('https://presseecho.test/de/category/gibt-es-nicht/feed')->assertNotFound(); $this->get('https://presseecho.test/de/category/veraltet/feed')->assertNotFound(); }); test('the english edition serves the feed under the english slug with english releases only', function () { /** @var TestCase $this */ $category = feedCategory(); feedRelease($category, 'deutsch'); feedRelease($category, 'english', ['language' => 'en', 'title' => 'English release']); $xml = (string) $this->get('https://presseecho.test/en/category/handel-en/feed')->assertSuccessful()->getContent(); expect($xml) ->toContain('Handel EN press releases – presseecho') ->toContain('en') ->toContain('https://presseecho.test/en/press-release/english'); $this->assertStringNotContainsString('press-release/deutsch', $xml); }); test('category and home pages announce the feeds for autodiscovery and link them', function () { /** @var TestCase $this */ $category = feedCategory(); feedRelease($category, 'irgendwas'); $this->get('https://presseecho.test/de/category/handel')->assertSuccessful() ->assertSee('', false) ->assertSee('', false) ->assertSee('href="https://presseecho.test/de/category/handel/feed" type="application/rss+xml"', false); $this->get('https://presseecho.test/de')->assertSuccessful() ->assertSee('type="application/rss+xml" title="RSS-Feed: alle Pressemitteilungen – presseecho" href="https://presseecho.test/de/feed"', false); $this->get('https://presseecho.test/llms.txt')->assertSuccessful() ->assertSee('https://presseecho.test/de/feed', false) ->assertSee('https://presseecho.test/de/category/{slug}/feed', false) ->assertSee('https://presseecho.test/de/newsroom/{slug}/feed', false) ->assertSee('https://presseecho.test/de/rss', false); }); test('the hub redirects feed addresses to the portal', function () { /** @var TestCase $this */ feedCategory(Portal::Both); $company = Company::factory()->businessportal24()->create(['slug' => 'nordwind', 'is_active' => true]); $this->get('https://pressekonto.test/de/feed') ->assertRedirect('https://presseecho.test/de/feed') ->assertStatus(301); $this->get('https://pressekonto.test/de/category/handel/feed') ->assertRedirect('https://presseecho.test/de/category/handel/feed') ->assertStatus(301); // Newsroom-Feed folgt dem Portal der Firma, nicht dem Standard-Portal. $this->get('https://pressekonto.test/de/newsroom/nordwind/feed') ->assertRedirect('https://businessportal24.test/de/newsroom/nordwind/feed') ->assertStatus(301); $this->get('https://pressekonto.test/de/newsroom/gibt-es-nicht/feed')->assertNotFound(); }); test('the newsroom feed lists the releases of one company on the own portal and follows the newsroom visibility', function () { /** @var TestCase $this */ $category = feedCategory(); $company = Company::factory()->presseecho()->create(['name' => 'Nordwind Energie AG', 'slug' => 'nordwind', 'is_active' => true, 'boilerplate' => '

Nordwind baut Windparks.

']); $other = Company::factory()->presseecho()->create(['slug' => 'andere', 'is_active' => true]); $mine = fn (string $slug, array $attributes = []) => PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($category)->for($company) ->create(['slug' => $slug, 'language' => 'de', 'published_at' => now()->subHour(), ...$attributes]); $mine('nordwind-neu', ['title' => 'Nordwind eröffnet Windpark']); $mine('nordwind-alt', ['published_at' => now()->subDays(2)]); $mine('nordwind-en', ['language' => 'en']); PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($category)->for($other)->create(['slug' => 'andere-firma', 'language' => 'de', 'published_at' => now()->subHour()]); $response = $this->get('https://presseecho.test/de/newsroom/nordwind/feed')->assertSuccessful(); $xml = (string) $response->getContent(); expect($xml) ->toContain('Pressemitteilungen von Nordwind Energie AG – presseecho') ->toContain('https://presseecho.test/de/newsroom/nordwind') ->toContain('') ->toContain('Nordwind baut Windparks.') ->toContain('press-release/nordwind-neu') ->toContain('press-release/nordwind-alt'); $this->assertStringNotContainsString('andere-firma', $xml); $this->assertStringNotContainsString('nordwind-en', $xml); // Ohne veröffentlichte Meldung keine öffentliche Seite — und kein Feed. $this->get('https://presseecho.test/de/newsroom/andere/feed')->assertSuccessful(); $empty = Company::factory()->presseecho()->create(['slug' => 'leer', 'is_active' => true]); $this->get('https://presseecho.test/de/newsroom/leer/feed')->assertNotFound(); // Deaktiviert oder entfernt: sofort weg, auch mit warmem Cache. $company->update(['is_active' => false]); $this->get('https://presseecho.test/de/newsroom/nordwind/feed')->assertNotFound(); $company->update(['is_active' => true, 'gone_at' => now()]); $this->get('https://presseecho.test/de/newsroom/nordwind/feed')->assertNotFound(); }); test('the newsroom page announces its feed for autodiscovery and links it', function () { /** @var TestCase $this */ $category = feedCategory(); $company = Company::factory()->presseecho()->create(['name' => 'Nordwind Energie AG', 'slug' => 'nordwind', 'is_active' => true]); PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($category)->for($company)->create(['slug' => 'nordwind-neu', 'language' => 'de', 'published_at' => now()->subHour()]); $this->get('https://presseecho.test/de/newsroom/nordwind')->assertSuccessful() ->assertSee('', false) ->assertSee('href="https://presseecho.test/de/newsroom/nordwind/feed" type="application/rss+xml"', false) ->assertSeeText('RSS-Feed'); }); test('the feed is cached with the page data and cleared with the public pages', function () { /** @var TestCase $this */ $category = feedCategory(); feedRelease($category, 'erste'); $this->get('https://presseecho.test/de/category/handel/feed')->assertSuccessful(); $key = WebPageCache::key('feed', ['presseecho', 'de', 'category', 'handel']); expect(Cache::has($key))->toBeTrue(); WebCaches::forgetPublicPages(); expect(Cache::has(WebPageCache::key('feed', ['presseecho', 'de', 'category', 'handel'])))->toBeFalse(); });