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