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();
});