presseportale/tests/Feature/HasUniqueSlugTest.php
Kevin Adametz 2aebb9d27e companies: Zusammenführung im Bestand, rückabwickelbar (Roadmap 1a.3)
companies:merge führt Firmen mit derselben Firmen-E-Mail zu einem Newsroom
zusammen — nur über Adressen, die 1a.2 als Dublette oder Namensvariante
klassifiziert hat. Es bleibt die Firma mit den meisten jemals
veröffentlichten Meldungen; Meldungen, Kontakte, Nutzer- und
Zahlungsverknüpfungen wandern per company_id, jede Bewegung steht in
company_merge_moves. companies:unmerge spielt sie rückwärts ab.

77 % der Gruppen sind dieselbe Firma auf beiden Portalen (Altsystem: eine
Firma je Portal). Der Überlebende wird dann `both`; die Meldungen behalten
ihr Portal. Der alte Newsroom-Slug führt per 301 zum Überlebenden.
Slug-Kollisionen auf dem anderen Portal werden übersprungen, nicht
umbenannt. Company::slugExists() kennt jetzt `both`.

Dev-DB: 14 142 Adressen, 16 194 Firmen, 13 297 Meldungen in 2 min;
Rückabwicklung in 1:15 min exakt auf den Stand davor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 08:43:46 +00:00

125 lines
4.4 KiB
PHP

<?php
use App\Enums\Portal;
use App\Models\Company;
use App\Models\PressRelease;
use Tests\TestCase;
test('press release slug is unique within portal and language scope', function () {
/** @var TestCase $this */
$base = (new PressRelease)->generateUniqueSlug('Test Mitteilung', [
'portal' => Portal::Presseecho->value,
'language' => 'de',
]);
expect($base)->toBe('test-mitteilung');
PressRelease::factory()->create([
'portal' => Portal::Presseecho->value,
'language' => 'de',
'slug' => 'test-mitteilung',
]);
$next = (new PressRelease)->generateUniqueSlug('Test Mitteilung', [
'portal' => Portal::Presseecho->value,
'language' => 'de',
]);
expect($next)->toBe('test-mitteilung-2');
// Same slug allowed in another portal
$foreign = (new PressRelease)->generateUniqueSlug('Test Mitteilung', [
'portal' => Portal::Businessportal24->value,
'language' => 'de',
]);
expect($foreign)->toBe('test-mitteilung');
// Same slug allowed in another language
$en = (new PressRelease)->generateUniqueSlug('Test Mitteilung', [
'portal' => Portal::Presseecho->value,
'language' => 'en',
]);
expect($en)->toBe('test-mitteilung');
});
test('press release editing keeps own slug stable', function () {
/** @var TestCase $this */
$pr = PressRelease::factory()->create([
'portal' => Portal::Presseecho->value,
'language' => 'de',
'slug' => 'meine-mitteilung',
]);
$regenerated = $pr->generateUniqueSlug('Meine Mitteilung', [
'portal' => Portal::Presseecho->value,
'language' => 'de',
]);
expect($regenerated)->toBe('meine-mitteilung');
});
test('a stale slug candidate is regenerated after a unique constraint collision', function () {
/** @var TestCase $this */
PressRelease::factory()->create([
'portal' => Portal::Presseecho->value,
'language' => 'de',
'slug' => 'gleichzeitig',
]);
$pressRelease = PressRelease::factory()->make([
'portal' => Portal::Presseecho->value,
'language' => 'de',
'title' => 'Gleichzeitig',
'slug' => 'gleichzeitig',
]);
$pressRelease->saveWithUniqueSlugRetry('Gleichzeitig', [
'portal' => Portal::Presseecho->value,
'language' => 'de',
]);
expect($pressRelease->exists)->toBeTrue()
->and($pressRelease->slug)->toBe('gleichzeitig-2');
});
test('company slug is unique within portal scope', function () {
/** @var TestCase $this */
Company::factory()->create([
'portal' => Portal::Presseecho->value,
'slug' => 'acme-gmbh',
]);
$slug = (new Company)->generateUniqueSlug('Acme GmbH', ['portal' => Portal::Presseecho->value]);
expect($slug)->toBe('acme-gmbh-2');
$other = (new Company)->generateUniqueSlug('Acme GmbH', ['portal' => Portal::Businessportal24->value]);
expect($other)->toBe('acme-gmbh');
});
test('a cross-portal company blocks its slug on every portal and needs it free everywhere', function () {
/** @var TestCase $this */
// Seit der Zusammenführung (Roadmap 1a.3) gibt es Firmen mit `both`; die
// öffentliche Auflösung fragt `portal IN (eigenes, both)`. Ein neuer
// PE-Slug darf deshalb nicht mit einer both-Firma zusammenfallen — sonst
// fände die Newsroom-URL zwei Firmen.
Company::factory()->create(['portal' => Portal::Both->value, 'slug' => 'acme-gmbh']);
Company::factory()->create(['portal' => Portal::Presseecho->value, 'slug' => 'beta-gmbh']);
expect((new Company)->generateUniqueSlug('Acme GmbH', ['portal' => Portal::Presseecho->value]))->toBe('acme-gmbh-2')
->and((new Company)->generateUniqueSlug('Acme GmbH', ['portal' => Portal::Businessportal24->value]))->toBe('acme-gmbh-2')
->and((new Company)->generateUniqueSlug('Beta GmbH', ['portal' => Portal::Both->value]))->toBe('beta-gmbh-2')
->and((new Company)->generateUniqueSlug('Beta GmbH', ['portal' => Portal::Businessportal24->value]))->toBe('beta-gmbh');
});
test('blank source falls back to model defined fallback', function () {
/** @var TestCase $this */
$prSlug = (new PressRelease)->generateUniqueSlug('---', [
'portal' => Portal::Presseecho->value,
'language' => 'de',
]);
expect($prSlug)->toBe('pressemitteilung');
$coSlug = (new Company)->generateUniqueSlug('!!!', ['portal' => Portal::Presseecho->value]);
expect($coSlug)->toBe('company');
});