create(['portal' => Portal::Presseecho, 'is_active' => true]);
$company = Company::factory()->presseecho()->create(['is_active' => true]);
return PressRelease::factory()
->published()
->forPortal(Portal::Presseecho)
->for($category)
->for($company)
->create(array_merge(['slug' => 'lastmod-pm', 'published_at' => Carbon::parse('2026-01-10 10:00:00')], $attributes));
}
test('view aggregation leaves updated_at untouched', function () {
/** @var TestCase $this */
$release = lastmodRelease(['hits' => 0]);
$before = PressRelease::withoutGlobalScopes()->find($release->id)->updated_at;
PressReleaseEvent::factory()->count(3)->create([
'press_release_id' => $release->id,
'type' => PressReleaseEventType::View,
]);
$this->travel(2)->days();
$this->artisan('press-releases:aggregate-views')->assertSuccessful();
$fresh = PressRelease::withoutGlobalScopes()->find($release->id);
expect($fresh->hits)->toBe(3)
->and($fresh->updated_at->equalTo($before))->toBeTrue();
});
test('a text change sets content_changed_at, other saves do not', function () {
$release = lastmodRelease();
expect($release->fresh()->content_changed_at)->toBeNull();
$this->travel(1)->days();
$release->update(['keywords' => 'nur, schlagworte']);
expect($release->fresh()->content_changed_at)->toBeNull();
$this->travel(1)->days();
$release->update(['text' => '
Neuer Text
']);
expect($release->fresh()->content_changed_at?->toDateTimeString())->toBe(now()->toDateTimeString());
});
test('sitemap lastmod follows content_changed_at and ignores updated_at', function () {
/** @var TestCase $this */
$release = lastmodRelease();
// Aufrufaggregation / Merge alter Art: updated_at springt, Inhalt nicht.
PressRelease::withoutGlobalScopes()->whereKey($release->id)->toBase()->update(['updated_at' => '2026-09-20 12:00:00']);
Cache::flush();
$xml = $this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()->getContent();
expect($xml)->toContain(''.Carbon::parse('2026-01-10 10:00:00')->toAtomString().'')
->not->toContain('2026-09-20');
PressRelease::withoutGlobalScopes()->whereKey($release->id)->toBase()->update(['content_changed_at' => '2026-03-05 08:00:00']);
Cache::flush();
$xml = $this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()->getContent();
expect($xml)->toContain(''.Carbon::parse('2026-03-05 08:00:00')->toAtomString().'');
});
test('the detail page reports content_changed_at as modification date', function () {
/** @var TestCase $this */
$release = lastmodRelease();
PressRelease::withoutGlobalScopes()->whereKey($release->id)->toBase()->update(['updated_at' => '2026-09-20 12:00:00']);
$html = $this->get('https://presseecho.test/de/press-release/lastmod-pm')->assertSuccessful()->getContent();
expect($html)->not->toContain('article:modified_time')
->not->toContain('2026-09-20T12:00:00');
PressRelease::withoutGlobalScopes()->whereKey($release->id)->toBase()->update(['content_changed_at' => '2026-03-05 08:00:00']);
Cache::flush();
$html = $this->get('https://presseecho.test/de/press-release/lastmod-pm')->assertSuccessful()->getContent();
$modified = Carbon::parse('2026-03-05 08:00:00')->toIso8601String();
expect($html)->toContain('')
->toContain('"dateModified":"'.str_replace('/', '\/', $modified).'"');
});
test('the sitemap lists english releases under the english edition', function () {
/** @var TestCase $this */
lastmodRelease(['slug' => 'english-release', 'language' => 'en']);
lastmodRelease(['slug' => 'deutsche-meldung', 'language' => 'de']);
$xml = $this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()->getContent();
expect($xml)->toContain('https://presseecho.test/en/press-release/english-release')
->toContain('https://presseecho.test/de/press-release/deutsche-meldung')
->not->toContain('/de/press-release/english-release');
$this->get('https://presseecho.test/en/press-release/english-release')->assertSuccessful();
});