22-05-2026 Optimierung der User und Admin Panels
This commit is contained in:
parent
d2ba22c0cf
commit
e8c47b7553
73 changed files with 10282 additions and 1546 deletions
|
|
@ -102,10 +102,70 @@ test('admin opens generated legacy invoice pdf inline', function () {
|
|||
LivewireVolt::test('admin.invoices.index')
|
||||
->assertSee('Öffnen');
|
||||
|
||||
$this->get(route('admin.legacy-invoices.pdf', $invoice))
|
||||
$response = $this->get(route('admin.legacy-invoices.pdf', $invoice));
|
||||
|
||||
$response
|
||||
->assertSuccessful()
|
||||
->assertHeader('content-type', 'application/pdf')
|
||||
->assertHeader('content-disposition', 'inline; filename="Presseecho-RNr-PE-2003.pdf"');
|
||||
|
||||
expect($response->content())
|
||||
->toContain('adametz.media')
|
||||
->toContain('Rechnungsbetrag')
|
||||
->toContain('/F2 21 Tf');
|
||||
|
||||
expect($invoice->refresh()->pdf_generated_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('admin opens legacy invoice pdf with stern layout for non media invoices', function () {
|
||||
/** @var TestCase $this */
|
||||
$invoice = LegacyInvoice::query()->create([
|
||||
'legacy_portal' => 'businessportal24',
|
||||
'legacy_id' => 2004,
|
||||
'user_id' => null,
|
||||
'legacy_user_id' => 504,
|
||||
'number' => 'BP-2004',
|
||||
'amount_cents' => 11900,
|
||||
'total_cents' => 11900,
|
||||
'status' => 'open',
|
||||
'invoice_date' => now()->subMonth(),
|
||||
'due_date' => now()->addDays(14),
|
||||
'payment_method' => 'invoice',
|
||||
'raw_snapshot' => [
|
||||
'number' => 'BP-2004',
|
||||
'is_media' => false,
|
||||
'service_period_begin_date' => now()->subYear()->toDateString(),
|
||||
'service_period_end_date' => now()->toDateString(),
|
||||
],
|
||||
'pdf_payload' => [
|
||||
'invoice' => [
|
||||
'is_media' => false,
|
||||
'service_period_begin_date' => now()->subYear()->toDateString(),
|
||||
'service_period_end_date' => now()->toDateString(),
|
||||
],
|
||||
'billing_address' => [
|
||||
'name' => 'Stern Archiv GmbH',
|
||||
'address' => 'Archivstrasse 2',
|
||||
'postal_code' => '10719',
|
||||
'city' => 'Berlin',
|
||||
'country_name' => 'Deutschland',
|
||||
],
|
||||
'payment_option_translation' => [
|
||||
'name' => 'Legacy Pressepaket',
|
||||
],
|
||||
],
|
||||
'imported_at' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('admin.legacy-invoices.pdf', $invoice));
|
||||
|
||||
$response
|
||||
->assertSuccessful()
|
||||
->assertHeader('content-type', 'application/pdf')
|
||||
->assertHeader('content-disposition', 'inline; filename="Businessportal24-RNr-BP-2004.pdf"');
|
||||
|
||||
expect($response->content())
|
||||
->toContain('Stern Consulting GmbH')
|
||||
->toContain('Hypo Vereinsbank')
|
||||
->toContain('/F2 19 Tf');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,8 +95,7 @@ test('admin can change archived press release back to another status', function
|
|||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pressRelease->id])
|
||||
->set('targetStatus', PressReleaseStatus::Draft->value)
|
||||
->call('changeStatus')
|
||||
->assertHasNoErrors()
|
||||
->assertSee('Status wurde auf');
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pressRelease->refresh();
|
||||
|
||||
|
|
|
|||
217
tests/Feature/Admin/AdminPressReleaseFormFieldsTest.php
Normal file
217
tests/Feature/Admin/AdminPressReleaseFormFieldsTest.php
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeFieldsAdmin(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('admin create persistiert subtitle und boilerplate_override', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create([
|
||||
'boilerplate' => 'Firmen-Boilerplate (default).',
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('portal', $company->portal->value)
|
||||
->set('categoryId', $category->id)
|
||||
->set('title', 'PM mit Subtitle und Boilerplate')
|
||||
->set('subtitle', 'Eine knackige Dachzeile als Untertitel.')
|
||||
->set('text', str_repeat('Inhalt eines Tests mit ausreichend Länge. ', 5))
|
||||
->set('useBoilerplateOverride', true)
|
||||
->set('boilerplateOverride', 'Override-Boilerplate nur für diese PM.')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr = PressRelease::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($pr->subtitle)->toBe('Eine knackige Dachzeile als Untertitel.');
|
||||
expect($pr->boilerplate_override)->toBe('Override-Boilerplate nur für diese PM.');
|
||||
});
|
||||
|
||||
test('admin create syncht ausgewählten Pressekontakt mit der PM', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'first_name' => 'Max',
|
||||
'last_name' => 'Mustermann',
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('portal', $company->portal->value)
|
||||
->set('categoryId', $category->id)
|
||||
->set('title', 'PM mit Kontakt')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('contactId', $contact->id)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr = PressRelease::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($pr->contacts()->pluck('contacts.id')->all())->toBe([$contact->id]);
|
||||
});
|
||||
|
||||
test('admin create setzt default-Kontakt beim Firma-Wechsel', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'first_name' => 'Anna',
|
||||
'last_name' => 'Aaron',
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->assertSet('contactId', $contact->id);
|
||||
});
|
||||
|
||||
test('admin create addTag und removeTag schreiben keywords kommagetrennt', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$component = LivewireVolt::test('admin.press-releases.create')
|
||||
->call('addTag', 'Innovation')
|
||||
->call('addTag', 'Mittelstand')
|
||||
->call('addTag', 'Innovation')
|
||||
->assertSet('keywords', 'Innovation, Mittelstand');
|
||||
|
||||
$component->call('removeTag', 'Innovation')
|
||||
->assertSet('keywords', 'Mittelstand');
|
||||
});
|
||||
|
||||
test('admin create lehnt zu langen Subtitle ab', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$category = Category::factory()->create();
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('portal', $company->portal->value)
|
||||
->set('categoryId', $category->id)
|
||||
->set('title', 'Gültiger Titel hier')
|
||||
->set('subtitle', str_repeat('a', 300))
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->call('save')
|
||||
->assertHasErrors(['subtitle']);
|
||||
});
|
||||
|
||||
test('admin edit hydratisiert subtitle, boilerplate_override und Kontakt', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'first_name' => 'Pia',
|
||||
'last_name' => 'Presse',
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'subtitle' => 'Hydrierter Untertitel.',
|
||||
'boilerplate_override' => 'Hydrierte Override-Boilerplate.',
|
||||
]);
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('subtitle', 'Hydrierter Untertitel.')
|
||||
->assertSet('boilerplateOverride', 'Hydrierte Override-Boilerplate.')
|
||||
->assertSet('useBoilerplateOverride', true)
|
||||
->assertSet('contactId', $contact->id);
|
||||
});
|
||||
|
||||
test('admin edit speichert subtitle, boilerplate_override und Kontakt', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->set('subtitle', 'Neuer Untertitel beim Bearbeiten.')
|
||||
->set('useBoilerplateOverride', true)
|
||||
->set('boilerplateOverride', 'Neue Override-Boilerplate beim Bearbeiten.')
|
||||
->set('contactId', $contact->id)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr->refresh();
|
||||
expect($pr->subtitle)->toBe('Neuer Untertitel beim Bearbeiten.');
|
||||
expect($pr->boilerplate_override)->toBe('Neue Override-Boilerplate beim Bearbeiten.');
|
||||
expect($pr->contacts()->pluck('contacts.id')->all())->toBe([$contact->id]);
|
||||
});
|
||||
|
||||
test('admin edit zeigt Pre-Submit-Check-Berechnungen', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'title' => 'Vollständiger Titel mit ausreichend Zeichen für ok-Status',
|
||||
'text' => str_repeat('Inhalt eines Tests mit ausreichend Länge. ', 25),
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSee('Pre-Submit-Check')
|
||||
->assertSee('Titel vorhanden')
|
||||
->assertSee('Mindestlänge Fließtext erreicht')
|
||||
->assertSee('Firma zugeordnet')
|
||||
->assertSee('Kategorie gewählt')
|
||||
->assertSee('Pressekontakt zugeordnet')
|
||||
->assertSee('Themen-Tags vergeben');
|
||||
});
|
||||
|
||||
test('admin edit zeigt Untertitel-Feld', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeFieldsAdmin();
|
||||
$pr = PressRelease::factory()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSee('Untertitel')
|
||||
->assertSee('Themen-Tags');
|
||||
});
|
||||
141
tests/Feature/Admin/AdminPressReleaseSchedulingTest.php
Normal file
141
tests/Feature/Admin/AdminPressReleaseSchedulingTest.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeAdmin(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('admin create form persistiert scheduled_at und embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$admin = makeAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$category = Category::factory()->create();
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('title', 'Admin Scheduled PM')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('companyId', $company->id)
|
||||
->set('categoryId', $category->id)
|
||||
->set('portal', $company->portal->value)
|
||||
->set('publishMode', 'scheduled')
|
||||
->set('scheduledAt', '2026-06-05T14:30')
|
||||
->set('useEmbargo', true)
|
||||
->set('embargoAt', '2026-06-10T08:00')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr = PressRelease::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00');
|
||||
expect($pr->embargo_at?->toDateTimeString())->toBe('2026-06-10 08:00:00');
|
||||
});
|
||||
|
||||
test('admin create form lehnt scheduled_at in der Vergangenheit ab', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$admin = makeAdmin();
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$category = Category::factory()->create();
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('title', 'Past Date PM')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('companyId', $company->id)
|
||||
->set('categoryId', $category->id)
|
||||
->set('portal', $company->portal->value)
|
||||
->set('publishMode', 'scheduled')
|
||||
->set('scheduledAt', '2026-05-15T10:00')
|
||||
->call('save')
|
||||
->assertHasErrors(['scheduledAt']);
|
||||
});
|
||||
|
||||
test('admin edit hydriert scheduled_at und embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$admin = makeAdmin();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => '2026-06-10 08:00:00',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('publishMode', 'scheduled')
|
||||
->assertSet('scheduledAt', '2026-06-05T14:30')
|
||||
->assertSet('useEmbargo', true)
|
||||
->assertSet('embargoAt', '2026-06-10T08:00');
|
||||
});
|
||||
|
||||
test('admin edit persistiert scheduled_at und embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$admin = makeAdmin();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'scheduled_at' => null,
|
||||
'embargo_at' => null,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->set('publishMode', 'scheduled')
|
||||
->set('scheduledAt', '2026-06-08T09:00')
|
||||
->set('useEmbargo', true)
|
||||
->set('embargoAt', '2026-06-12T12:00')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr->refresh();
|
||||
|
||||
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-08 09:00:00');
|
||||
expect($pr->embargo_at?->toDateTimeString())->toBe('2026-06-12 12:00:00');
|
||||
});
|
||||
|
||||
test('admin publishMode now clears scheduled_at on save', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$admin = makeAdmin();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => '2026-06-10 08:00:00',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->set('publishMode', 'now')
|
||||
->set('useEmbargo', false)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr->refresh();
|
||||
|
||||
expect($pr->scheduled_at)->toBeNull();
|
||||
expect($pr->embargo_at)->toBeNull();
|
||||
});
|
||||
124
tests/Feature/Admin/AdminPressReleaseShowTest.php
Normal file
124
tests/Feature/Admin/AdminPressReleaseShowTest.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\PressReleaseStatusLog;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeAdminForShow(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('admin show rendert Rejection-Banner mit letzter Begründung', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Rejected->value,
|
||||
]);
|
||||
PressReleaseStatusLog::query()->create([
|
||||
'press_release_id' => $pr->id,
|
||||
'changed_by_user_id' => $admin->id,
|
||||
'from_status' => PressReleaseStatus::Review->value,
|
||||
'to_status' => PressReleaseStatus::Rejected->value,
|
||||
'reason' => 'Werbliche Sprache, bitte überarbeiten.',
|
||||
'source' => 'admin',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Diese Pressemitteilung wurde abgelehnt')
|
||||
->assertSee('Werbliche Sprache, bitte überarbeiten.');
|
||||
});
|
||||
|
||||
test('admin show zeigt zugeordnete Pressekontakte', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'first_name' => 'Max',
|
||||
'last_name' => 'Mustermann',
|
||||
'responsibility' => 'Pressesprecher',
|
||||
'email' => 'presse@example.test',
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
|
||||
$pr = PressRelease::factory()->create(['company_id' => $company->id]);
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Zugeordnete Pressekontakte')
|
||||
->assertSee('Max Mustermann')
|
||||
->assertSee('Pressesprecher')
|
||||
->assertSee('presse@example.test');
|
||||
});
|
||||
|
||||
test('admin show zeigt Hinweis bei fehlenden Kontakten', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create();
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Dieser Pressemitteilung ist kein Pressekontakt zugeordnet.');
|
||||
});
|
||||
|
||||
test('admin show zeigt Scheduling-Termin im Review-Workflow', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-15 10:00:00',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Geplante Veröffentlichung')
|
||||
->assertSee('15.06.2026 10:00');
|
||||
});
|
||||
|
||||
test('admin show zeigt Embargo-Info im Published-Workflow', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'published_at' => '2026-06-01 10:00:00',
|
||||
'embargo_at' => now()->addDays(10),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Sperrfrist bis');
|
||||
});
|
||||
|
||||
test('admin show zeigt Autor im Status-Verlauf-Grid', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShow();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$author = User::factory()->create(['name' => 'Anna Autorin']);
|
||||
$pr = PressRelease::factory()->create(['user_id' => $author->id]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Anna Autorin');
|
||||
});
|
||||
|
|
@ -7,6 +7,7 @@ use App\Models\Profile;
|
|||
use App\Models\User;
|
||||
use App\Models\UserFilterPreset;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
|
@ -555,6 +556,61 @@ test('admin can open user details modal from users index and see company link st
|
|||
->assertSet('contactLookup', '');
|
||||
});
|
||||
|
||||
test('admin users index free text search matches names and email parts', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('admin');
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Seabstian Einrock',
|
||||
'email' => 'info@connectar.de',
|
||||
])->assignRole('customer');
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Barbara Barr',
|
||||
'email' => 'barbara@example.com',
|
||||
])->assignRole('customer');
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->set('search', 'Seabstian')
|
||||
->assertSee('info@connectar.de')
|
||||
->assertDontSee('barbara@example.com')
|
||||
->set('search', 'Einrock')
|
||||
->assertSee('info@connectar.de')
|
||||
->assertDontSee('barbara@example.com')
|
||||
->set('search', 'connectar.de')
|
||||
->assertSee('Seabstian Einrock')
|
||||
->assertDontSee('Barbara Barr')
|
||||
->set('search', 'Seabstian Einrock info@connectar.de')
|
||||
->assertSee('info@connectar.de')
|
||||
->assertDontSee('barbara@example.com')
|
||||
->set('search', 'Barr')
|
||||
->assertSee('barbara@example.com')
|
||||
->assertDontSee('info@connectar.de');
|
||||
});
|
||||
|
||||
test('admin users index uses full count pagination', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('admin');
|
||||
|
||||
User::factory()->count(51)->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->assertViewHas('users', fn ($users): bool => $users instanceof LengthAwarePaginator
|
||||
&& $users->perPage() === 50
|
||||
&& $users->total() === 52
|
||||
&& $users->lastPage() === 2);
|
||||
});
|
||||
|
||||
test('admin users index supports workflow filters and quality badges', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ use Database\Seeders\RolesAndPermissionsSeeder;
|
|||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
|
|
@ -75,6 +76,31 @@ test('press release list and KPI counts render when data exists', function () {
|
|||
->assertSee('Alle anzeigen');
|
||||
});
|
||||
|
||||
test('company dashboard preview is limited to the ten newest companies', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
for ($i = 1; $i <= 12; $i++) {
|
||||
Company::factory()->create([
|
||||
'owner_user_id' => $customer->id,
|
||||
'name' => sprintf('Dashboard Firma %02d', $i),
|
||||
'created_at' => now()->subDays(13 - $i),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.dashboard')
|
||||
->assertSee('12 zugeordnet')
|
||||
->assertSee('Dashboard Firma 12')
|
||||
->assertSee('Dashboard Firma 03')
|
||||
->assertDontSee('Dashboard Firma 02')
|
||||
->assertDontSee('Dashboard Firma 01')
|
||||
->assertSee('Die zehn neuesten Firmen werden hier als Vorschau angezeigt.')
|
||||
->assertSee(route('me.press-kits.index'), false);
|
||||
});
|
||||
|
||||
test('profile completeness hint with percentage appears for partial profiles', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,28 @@ test('customer company switcher links to selected company detail', function () {
|
|||
->assertSee(route('me.press-kits.show', $company->id, absolute: false));
|
||||
});
|
||||
|
||||
test('customer company switcher limits rendered company options', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
||||
for ($i = 1; $i <= 55; $i++) {
|
||||
Company::factory()->presseecho()->create([
|
||||
'name' => sprintf('Switcher Firma %02d', $i),
|
||||
'owner_user_id' => $customer->id,
|
||||
'created_at' => now()->subDays(56 - $i),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.company-switcher')
|
||||
->assertViewHas('companies', fn ($companies) => $companies->count() === 50)
|
||||
->assertSee('Switcher Firma 55')
|
||||
->assertSee('Switcher Firma 06')
|
||||
->assertDontSee('Switcher Firma 05')
|
||||
->assertSee('Weitere Firmen über');
|
||||
});
|
||||
|
||||
test('customer press release list is filtered by selected company context', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
|
|
|||
105
tests/Feature/CustomerPressKitCreatePhase8eTest.php
Normal file
105
tests/Feature/CustomerPressKitCreatePhase8eTest.php
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeCustomerForCreate(): User
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
test('Create-Route ist für eingeloggte Customer erreichbar', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForCreate();
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get('/admin/me/firmen/anlegen')
|
||||
->assertOk()
|
||||
->assertSeeText('Neue Firma anlegen')
|
||||
->assertSeeText('Firmenname');
|
||||
});
|
||||
|
||||
test('Create-Route ist für Gäste nicht zugänglich', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->get('/admin/me/firmen/anlegen')
|
||||
->assertRedirect();
|
||||
});
|
||||
|
||||
test('save erstellt eine Firma und ordnet sie dem User als Owner zu', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForCreate();
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.create')
|
||||
->set('name', 'Neue Brauerei AG')
|
||||
->set('portal', 'presseecho')
|
||||
->set('type', 'company')
|
||||
->set('email', 'kontakt@brauerei.de')
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect();
|
||||
|
||||
/** @var Company $company */
|
||||
$company = Company::query()->withoutGlobalScopes()->where('name', 'Neue Brauerei AG')->first();
|
||||
|
||||
expect($company)->not->toBeNull()
|
||||
->and($company->owner_user_id)->toBe($customer->id)
|
||||
->and($company->portal->value)->toBe('presseecho')
|
||||
->and($company->email)->toBe('kontakt@brauerei.de')
|
||||
->and($company->is_active)->toBeTrue();
|
||||
|
||||
expect($customer->companies()->where('companies.id', $company->id)->wherePivot('role', 'owner')->exists())
|
||||
->toBeTrue();
|
||||
});
|
||||
|
||||
test('save validiert Pflichtfelder Name und Portal', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForCreate();
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.create')
|
||||
->set('name', '')
|
||||
->set('portal', '')
|
||||
->call('save')
|
||||
->assertHasErrors(['name' => 'required', 'portal' => 'required']);
|
||||
});
|
||||
|
||||
test('save lehnt unbekannte Portal-Werte ab', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForCreate();
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.create')
|
||||
->set('name', 'Test GmbH')
|
||||
->set('portal', 'invalid')
|
||||
->call('save')
|
||||
->assertHasErrors(['portal']);
|
||||
});
|
||||
|
||||
test('save akzeptiert Portal Both', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForCreate();
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.create')
|
||||
->set('name', 'Beide Portale GmbH')
|
||||
->set('portal', 'both')
|
||||
->set('type', 'company')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect(Company::query()->withoutGlobalScopes()->where('name', 'Beide Portale GmbH')->value('portal'))
|
||||
->toBe(Portal::Both);
|
||||
});
|
||||
309
tests/Feature/CustomerPressKitIndexPhase8eTest.php
Normal file
309
tests/Feature/CustomerPressKitIndexPhase8eTest.php
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{customer: User}
|
||||
*/
|
||||
function makeCustomerForPressKitsIndex(): array
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
return ['customer' => $customer];
|
||||
}
|
||||
|
||||
test('Index zeigt Counter-Strip mit Firmen, aktiven, PMs und Kontakten', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$activeOne = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($activeOne->id, ['role' => 'owner']);
|
||||
Contact::factory()->count(2)->for($activeOne)->create(['portal' => $activeOne->portal->value]);
|
||||
PressRelease::factory()->count(3)->for($activeOne)->create([
|
||||
'user_id' => $customer->id,
|
||||
'portal' => $activeOne->portal->value,
|
||||
'status' => 'draft',
|
||||
]);
|
||||
|
||||
$activeTwo = Company::factory()->businessportal24()->create();
|
||||
$customer->companies()->attach($activeTwo->id, ['role' => 'responsible']);
|
||||
Contact::factory()->for($activeTwo)->create(['portal' => $activeTwo->portal->value]);
|
||||
|
||||
$inactive = Company::factory()->presseecho()->inactive()->create();
|
||||
$customer->companies()->attach($inactive->id, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get('/admin/me/firmen')
|
||||
->assertOk()
|
||||
->assertSeeInOrder(['<b>3</b>', 'Firmen'], false)
|
||||
->assertSeeInOrder(['<b>2</b>', 'aktiv'], false)
|
||||
->assertSeeInOrder(['<b>3</b>', 'Pressemitteilungen gesamt'], false)
|
||||
->assertSeeInOrder(['<b>3</b>', 'Pressekontakte hinterlegt'], false);
|
||||
});
|
||||
|
||||
test('Saved-View Aktiv filtert nur is_active = true Firmen', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$active = Company::factory()->presseecho()->create(['name' => 'AlphaAktiv GmbH']);
|
||||
$customer->companies()->attach($active->id, ['role' => 'owner']);
|
||||
|
||||
$inactive = Company::factory()->presseecho()->inactive()->create(['name' => 'ZetaInaktiv GmbH']);
|
||||
$customer->companies()->attach($inactive->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setSavedView', 'active')
|
||||
->assertSet('savedView', 'active')
|
||||
->assertSee('AlphaAktiv GmbH')
|
||||
->assertDontSee('ZetaInaktiv GmbH');
|
||||
});
|
||||
|
||||
test('Saved-View Inaktiv filtert nur is_active = false Firmen', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$active = Company::factory()->presseecho()->create(['name' => 'AlphaAktiv GmbH']);
|
||||
$customer->companies()->attach($active->id, ['role' => 'owner']);
|
||||
|
||||
$inactive = Company::factory()->presseecho()->inactive()->create(['name' => 'ZetaInaktiv GmbH']);
|
||||
$customer->companies()->attach($inactive->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setSavedView', 'inactive')
|
||||
->assertSee('ZetaInaktiv GmbH')
|
||||
->assertDontSee('AlphaAktiv GmbH');
|
||||
});
|
||||
|
||||
test('Saved-View Geteilt zeigt nur Firmen, bei denen User nicht Owner ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$owner = User::factory()->create();
|
||||
|
||||
$ownCompany = Company::factory()->presseecho()->create([
|
||||
'name' => 'EigeneFirma GmbH',
|
||||
'owner_user_id' => $customer->id,
|
||||
]);
|
||||
|
||||
$sharedCompany = Company::factory()->presseecho()->create([
|
||||
'name' => 'GeteilteFirma GmbH',
|
||||
'owner_user_id' => $owner->id,
|
||||
]);
|
||||
$customer->companies()->attach($sharedCompany->id, ['role' => 'responsible']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setSavedView', 'shared')
|
||||
->assertSee('GeteilteFirma GmbH')
|
||||
->assertDontSee('EigeneFirma GmbH');
|
||||
});
|
||||
|
||||
test('Portal-Filter zeigt nur Firmen des gewählten Portals (oder both)', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$pe = Company::factory()->presseecho()->create(['name' => 'PresseechoCo GmbH']);
|
||||
$bp = Company::factory()->businessportal24()->create(['name' => 'BusinessportalCo GmbH']);
|
||||
$customer->companies()->attach([
|
||||
$pe->id => ['role' => 'owner'],
|
||||
$bp->id => ['role' => 'owner'],
|
||||
]);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setPortalFilter', 'presseecho')
|
||||
->assertSee('PresseechoCo GmbH')
|
||||
->assertDontSee('BusinessportalCo GmbH');
|
||||
});
|
||||
|
||||
test('Rollen-Filter Owner zeigt nur Firmen, in denen User Owner ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$otherOwner = User::factory()->create();
|
||||
|
||||
$owned = Company::factory()->presseecho()->create([
|
||||
'name' => 'EigeneFirma GmbH',
|
||||
'owner_user_id' => $customer->id,
|
||||
]);
|
||||
|
||||
$memberCompany = Company::factory()->presseecho()->create([
|
||||
'name' => 'MitgliedFirma GmbH',
|
||||
'owner_user_id' => $otherOwner->id,
|
||||
]);
|
||||
$customer->companies()->attach($memberCompany->id, ['role' => 'member']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setRoleFilter', 'owner')
|
||||
->assertSee('EigeneFirma GmbH')
|
||||
->assertDontSee('MitgliedFirma GmbH');
|
||||
});
|
||||
|
||||
test('Suche filtert auf Firmennamen', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$alpha = Company::factory()->presseecho()->create(['name' => 'Alpha Brauerei AG']);
|
||||
$beta = Company::factory()->presseecho()->create(['name' => 'Beta Verlag GmbH']);
|
||||
$customer->companies()->attach([
|
||||
$alpha->id => ['role' => 'owner'],
|
||||
$beta->id => ['role' => 'owner'],
|
||||
]);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->set('search', 'Brauerei')
|
||||
->assertSee('Alpha Brauerei AG')
|
||||
->assertDontSee('Beta Verlag GmbH');
|
||||
});
|
||||
|
||||
test('View-Mode kann auf list umgeschaltet werden', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$company = Company::factory()->presseecho()->create(['name' => 'FirmaA GmbH']);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->call('setViewMode', 'list')
|
||||
->assertSet('viewMode', 'list')
|
||||
->assertSee('FirmaA GmbH')
|
||||
->assertSee('cursor-pointer', false)
|
||||
->assertSee('firm-list-actions', false);
|
||||
});
|
||||
|
||||
test('Empty-State noch-keine-Firma wird ohne Filter und ohne Firmen gezeigt', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get('/admin/me/firmen')
|
||||
->assertOk()
|
||||
->assertSeeText('Noch keine Firma angelegt');
|
||||
});
|
||||
|
||||
test('Empty-State Filter-ohne-Treffer wird bei aktiven Filtern ohne Match gezeigt', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$company = Company::factory()->presseecho()->create(['name' => 'Alpha GmbH']);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->set('search', 'Zeta')
|
||||
->assertSee('Keine Firmen mit diesen Filtern');
|
||||
});
|
||||
|
||||
test('Filter zurücksetzen leert alle Filter und Suche', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$company = Company::factory()->presseecho()->create(['name' => 'Alpha GmbH']);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->set('search', 'Zeta')
|
||||
->call('setSavedView', 'inactive')
|
||||
->call('setPortalFilter', 'presseecho')
|
||||
->call('setRoleFilter', 'member')
|
||||
->call('resetFilters')
|
||||
->assertSet('search', '')
|
||||
->assertSet('savedView', 'all')
|
||||
->assertSet('portalFilter', '')
|
||||
->assertSet('roleFilter', 'all')
|
||||
->assertSee('Alpha GmbH');
|
||||
});
|
||||
|
||||
test('Add-Tile wird nur auf der letzten Seite des Card-Grids gerendert', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$company = Company::factory()->presseecho()->create(['name' => 'Alpha GmbH']);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->assertSee('Neue Firma anlegen');
|
||||
});
|
||||
|
||||
test('Firmenübersicht paginiert mit 50 Firmen pro Seite', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
for ($i = 1; $i <= 51; $i++) {
|
||||
Company::factory()->presseecho()->create([
|
||||
'name' => sprintf('Paginiert Firma %02d', $i),
|
||||
'owner_user_id' => $customer->id,
|
||||
]);
|
||||
}
|
||||
|
||||
LivewireVolt::actingAs($customer)
|
||||
->test('customer.press-kits.index')
|
||||
->assertViewHas('pressKits', fn ($pressKits) => $pressKits->perPage() === 50 && $pressKits->count() === 50)
|
||||
->assertSee('portal-pagination', false)
|
||||
->assertSee('aria-current="page"', false)
|
||||
->assertSee('cursor-pointer', false)
|
||||
->assertSee('Paginiert Firma 01')
|
||||
->assertSee('Paginiert Firma 50')
|
||||
->assertDontSee('Paginiert Firma 51');
|
||||
});
|
||||
|
||||
test('Karte zeigt Status Aktiv, Portal-Pills und KPIs', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
$company = Company::factory()->presseecho()->create([
|
||||
'name' => 'Brauerei AG',
|
||||
'is_active' => true,
|
||||
]);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
Contact::factory()->count(2)->for($company)->create(['portal' => $company->portal->value]);
|
||||
PressRelease::factory()->count(4)->for($company)->create([
|
||||
'user_id' => $customer->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => 'draft',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get('/admin/me/firmen')
|
||||
->assertOk()
|
||||
->assertSeeText('Aktiv')
|
||||
->assertSeeText('Brauerei AG')
|
||||
->assertSee('presseecho')
|
||||
->assertSeeText('PMs');
|
||||
});
|
||||
|
||||
test('Rollen-Legende wird unterhalb der Liste angezeigt', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer] = makeCustomerForPressKitsIndex();
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get('/admin/me/firmen')
|
||||
->assertOk()
|
||||
->assertSeeText('Rollen pro Firma');
|
||||
});
|
||||
|
||||
test('Performance-Indexe für die Firmenübersicht sind vorhanden', function () {
|
||||
$companyIndexes = collect(Schema::getIndexes('companies'))->pluck('name');
|
||||
$pressReleaseIndexes = collect(Schema::getIndexes('press_releases'))->pluck('name');
|
||||
|
||||
expect($companyIndexes)
|
||||
->toContain('companies_owner_name_id_idx')
|
||||
->toContain('companies_owner_active_name_id_idx')
|
||||
->and($pressReleaseIndexes)
|
||||
->toContain('press_releases_company_published_idx')
|
||||
->toContain('press_releases_user_created_id_idx')
|
||||
->toContain('press_releases_user_status_created_idx');
|
||||
});
|
||||
|
|
@ -97,7 +97,7 @@ test('save with all required fields persists the press release and syncs contact
|
|||
expect($pr->contacts->first()->id)->toBe($contact->id);
|
||||
});
|
||||
|
||||
test('save without a contact id fails validation', function () {
|
||||
test('save without a contact id succeeds and leaves contacts empty', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
|
@ -113,7 +113,28 @@ test('save without a contact id fails validation', function () {
|
|||
->set('text', str_repeat('x', 60))
|
||||
->set('categoryId', $category->id)
|
||||
->call('save', 'draft')
|
||||
->assertHasErrors(['contactId']);
|
||||
->assertHasNoErrors(['contactId']);
|
||||
|
||||
$pr = PressRelease::query()->where('title', 'Titel mit genug Zeichen')->firstOrFail();
|
||||
expect($pr->contacts()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('presubmit check for missing contact is warning, not error', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
$component = LivewireVolt::test('customer.press-releases.create')
|
||||
->set('contactId', null);
|
||||
|
||||
$checks = collect($component->instance()->presubmitChecks);
|
||||
$contactCheck = $checks->firstWhere('key', 'contact');
|
||||
|
||||
expect($contactCheck['status'])->toBe('warn');
|
||||
});
|
||||
|
||||
test('boilerplate override is null when toggle is off even if text is filled', function () {
|
||||
|
|
|
|||
190
tests/Feature/CustomerPressReleaseEditPhase7Test.php
Normal file
190
tests/Feature/CustomerPressReleaseEditPhase7Test.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeCustomerWithPressRelease(array $prAttributes = []): array
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$pr = PressRelease::factory()->create(array_merge([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => 'draft',
|
||||
], $prAttributes));
|
||||
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
return compact('customer', 'company', 'contact', 'category', 'pr');
|
||||
}
|
||||
|
||||
test('mount loads all Phase 7 fields and pivot contact', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr, 'contact' => $contact] = makeCustomerWithPressRelease([
|
||||
'subtitle' => 'Untertitel der PM',
|
||||
'boilerplate_override' => 'Spezielle Boilerplate.',
|
||||
'keywords' => 'Test, Brauerei',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('subtitle', 'Untertitel der PM')
|
||||
->assertSet('boilerplateOverride', 'Spezielle Boilerplate.')
|
||||
->assertSet('useBoilerplateOverride', true)
|
||||
->assertSet('keywords', 'Test, Brauerei')
|
||||
->assertSet('contactId', $contact->id)
|
||||
->assertSet('currentStatus', 'draft');
|
||||
});
|
||||
|
||||
test('mount falls back to first company contact when no pivot exists', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr, 'contact' => $contact] = makeCustomerWithPressRelease();
|
||||
|
||||
$pr->contacts()->detach();
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('contactId', $contact->id);
|
||||
});
|
||||
|
||||
test('save persists all new Phase 7 fields and syncs contact', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease();
|
||||
$newContact = Contact::factory()->for($pr->company)->create([
|
||||
'portal' => $pr->company->portal->value,
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->set('title', 'Aktualisierter Titel mit genug Zeichen')
|
||||
->set('subtitle', 'Neue Subline')
|
||||
->set('text', str_repeat('Aktualisierter Fließtext mit etwas Inhalt. ', 5))
|
||||
->set('keywords', 'Neu, Frisch')
|
||||
->set('contactId', $newContact->id)
|
||||
->set('useBoilerplateOverride', true)
|
||||
->set('boilerplateOverride', 'Override-Text für diese PM.')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr->refresh();
|
||||
|
||||
expect($pr->title)->toBe('Aktualisierter Titel mit genug Zeichen');
|
||||
expect($pr->subtitle)->toBe('Neue Subline');
|
||||
expect($pr->boilerplate_override)->toBe('Override-Text für diese PM.');
|
||||
expect($pr->keywords)->toBe('Neu, Frisch');
|
||||
expect($pr->contacts->pluck('id')->all())->toBe([$newContact->id]);
|
||||
});
|
||||
|
||||
test('save without contact id succeeds and detaches existing contact', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease();
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->set('contactId', null)
|
||||
->call('save')
|
||||
->assertHasNoErrors(['contactId']);
|
||||
|
||||
expect($pr->fresh()->contacts()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('save nulls boilerplate_override when toggle is turned off', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease([
|
||||
'boilerplate_override' => 'Alter Override-Text',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('useBoilerplateOverride', true)
|
||||
->set('useBoilerplateOverride', false)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr->refresh();
|
||||
|
||||
expect($pr->boilerplate_override)->toBeNull();
|
||||
});
|
||||
|
||||
test('changing the company resets the contact to the new company default', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease();
|
||||
|
||||
$secondCompany = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($secondCompany->id, ['role' => 'owner']);
|
||||
$secondContact = Contact::factory()->for($secondCompany)->create([
|
||||
'portal' => $secondCompany->portal->value,
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->set('companyId', $secondCompany->id)
|
||||
->assertSet('contactId', $secondContact->id);
|
||||
});
|
||||
|
||||
test('rejected press releases can be edited and re-submitted', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease([
|
||||
'status' => 'rejected',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('currentStatus', 'rejected');
|
||||
});
|
||||
|
||||
test('foreign press release returns 403 on mount', function () {
|
||||
/** @var TestCase $this */
|
||||
['pr' => $pr] = makeCustomerWithPressRelease();
|
||||
$stranger = User::factory()->create(['is_active' => true]);
|
||||
$stranger->assignRole('customer');
|
||||
|
||||
$this->actingAs($stranger);
|
||||
|
||||
$this->get(route('me.press-releases.edit', $pr->id))
|
||||
->assertNotFound();
|
||||
});
|
||||
|
||||
test('addTag and removeTag work in edit form', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerWithPressRelease([
|
||||
'keywords' => 'Eins',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->call('addTag', 'Zwei')
|
||||
->assertSet('keywords', 'Eins, Zwei')
|
||||
->call('removeTag', 'Eins')
|
||||
->assertSet('keywords', 'Zwei');
|
||||
});
|
||||
135
tests/Feature/CustomerPressReleaseSchedulingFormTest.php
Normal file
135
tests/Feature/CustomerPressReleaseSchedulingFormTest.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeSchedulingCustomer(): array
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
return compact('customer', 'company', 'contact', 'category');
|
||||
}
|
||||
|
||||
test('create form persistiert scheduled_at und embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
['customer' => $customer, 'category' => $category] = makeSchedulingCustomer();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('title', 'Phase 7F Scheduling Demo')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('categoryId', $category->id)
|
||||
->set('publishMode', 'scheduled')
|
||||
->set('scheduledAt', '2026-06-05T14:30')
|
||||
->set('useEmbargo', true)
|
||||
->set('embargoAt', '2026-06-10T08:00')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr = PressRelease::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00');
|
||||
expect($pr->embargo_at?->toDateTimeString())->toBe('2026-06-10 08:00:00');
|
||||
});
|
||||
|
||||
test('create form lehnt scheduled_at in der Vergangenheit ab', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
['customer' => $customer, 'category' => $category] = makeSchedulingCustomer();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('title', 'Vergangene Veröffentlichung')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('categoryId', $category->id)
|
||||
->set('publishMode', 'scheduled')
|
||||
->set('scheduledAt', '2026-05-30T10:00')
|
||||
->call('save')
|
||||
->assertHasErrors(['scheduledAt']);
|
||||
});
|
||||
|
||||
test('create form lehnt embargo_at in der Vergangenheit ab', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
['customer' => $customer, 'category' => $category] = makeSchedulingCustomer();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('title', 'Vergangenes Embargo')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('categoryId', $category->id)
|
||||
->set('useEmbargo', true)
|
||||
->set('embargoAt', '2026-05-30T10:00')
|
||||
->call('save')
|
||||
->assertHasErrors(['embargoAt']);
|
||||
});
|
||||
|
||||
test('publishMode now setzt scheduled_at auf null beim Save', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'category' => $category] = makeSchedulingCustomer();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('title', 'Sofort veröffentlichen')
|
||||
->set('text', str_repeat('Inhalt eines Tests. ', 5))
|
||||
->set('categoryId', $category->id)
|
||||
->set('publishMode', 'now')
|
||||
->set('scheduledAt', '2026-12-31T12:00')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$pr = PressRelease::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($pr->scheduled_at)->toBeNull();
|
||||
});
|
||||
|
||||
test('edit form hydriert scheduled_at und embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
['customer' => $customer, 'company' => $company, 'contact' => $contact, 'category' => $category] = makeSchedulingCustomer();
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => 'draft',
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => '2026-06-10 08:00:00',
|
||||
]);
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertSet('publishMode', 'scheduled')
|
||||
->assertSet('scheduledAt', '2026-06-05T14:30')
|
||||
->assertSet('useEmbargo', true)
|
||||
->assertSet('embargoAt', '2026-06-10T08:00');
|
||||
});
|
||||
|
|
@ -10,9 +10,7 @@ use App\Models\PressRelease;
|
|||
use App\Models\PressReleaseStatusLog;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -61,82 +59,43 @@ test('customer can update own profile fields', function () {
|
|||
expect($customer->billingAddress?->country_code)->toBe('DE');
|
||||
});
|
||||
|
||||
test('customer can update an owned company and upload a logo with variants', function () {
|
||||
test('customer profile keeps company management out of the profile page', function () {
|
||||
/** @var TestCase $this */
|
||||
Storage::fake('public');
|
||||
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$company = Company::factory()->presseecho()->create([
|
||||
'owner_user_id' => $customer->id,
|
||||
'name' => 'Old Co',
|
||||
'name' => 'Nicht im Profil geladene Firma',
|
||||
]);
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$logo = UploadedFile::fake()->image('logo.png', 800, 400);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('editableCompanyId', $company->id)
|
||||
->set('companyName', 'New Co GmbH')
|
||||
->set('companyEmail', 'contact@example.test')
|
||||
->set('companyWebsite', 'https://example.test')
|
||||
->set('companyCountryCode', 'CH')
|
||||
->set('companyLogo', $logo)
|
||||
->call('saveCompany')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$company->refresh();
|
||||
|
||||
expect($company->name)->toBe('New Co GmbH');
|
||||
expect($company->email)->toBe('contact@example.test');
|
||||
expect($company->country_code)->toBe('CH');
|
||||
expect($company->logo_path)->not->toBeNull();
|
||||
expect($company->logo_variants)->toHaveKey('sq');
|
||||
expect($company->logo_variants)->toHaveKey('wide');
|
||||
|
||||
expect(Storage::disk('public')->exists($company->logo_path))->toBeTrue();
|
||||
expect(Storage::disk('public')->exists($company->logo_variants['sq']))->toBeTrue();
|
||||
expect(Storage::disk('public')->exists($company->logo_variants['wide']))->toBeTrue();
|
||||
->assertSee('Rechnungsadresse')
|
||||
->assertSee('Profileinstellungen')
|
||||
->assertSee('Firmen verwalten')
|
||||
->assertDontSee('Zugeordnete Firmen')
|
||||
->assertDontSee('Nicht im Profil geladene Firma');
|
||||
});
|
||||
|
||||
test('customer cannot edit a company they do not own or co-manage', function () {
|
||||
test('customer can save profile settings without a billing address', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$otherCompany = Company::factory()->presseecho()->create([
|
||||
'owner_user_id' => User::factory()->create()->id,
|
||||
]);
|
||||
$customer->companies()->attach($otherCompany->id, ['role' => 'member']);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('editableCompanyId', $otherCompany->id)
|
||||
->set('companyName', 'Hijacked')
|
||||
->call('saveCompany');
|
||||
|
||||
$otherCompany->refresh();
|
||||
|
||||
expect($otherCompany->name)->not->toBe('Hijacked');
|
||||
});
|
||||
|
||||
test('customer can edit a company they own directly without pivot membership', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$company = Company::factory()->presseecho()->create([
|
||||
'owner_user_id' => $customer->id,
|
||||
'name' => 'Owner Company',
|
||||
$customer = User::factory()->create([
|
||||
'is_active' => true,
|
||||
'name' => 'Ohne Rechnung',
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('editableCompanyId', $company->id)
|
||||
->set('companyName', 'Owner Company Updated')
|
||||
->call('saveCompany')
|
||||
->set('name', 'Nur Profil')
|
||||
->set('firstName', 'Nur')
|
||||
->set('lastName', 'Profil')
|
||||
->call('saveProfile')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($company->refresh()->name)->toBe('Owner Company Updated');
|
||||
expect($customer->refresh()->name)->toBe('Nur Profil');
|
||||
expect($customer->billingAddress)->toBeNull();
|
||||
});
|
||||
|
||||
test('customer security page renders password and email forms', function () {
|
||||
|
|
@ -221,6 +180,7 @@ test('customer press releases derive portal from selected company', function ()
|
|||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
$contact = Contact::factory()->for($company)->create(['portal' => $company->portal->value]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
|
|
@ -228,6 +188,7 @@ test('customer press releases derive portal from selected company', function ()
|
|||
->set('companyId', $company->id)
|
||||
->set('portal', Portal::Businessportal24->value)
|
||||
->set('categoryId', Category::factory()->create()->id)
|
||||
->set('contactId', $contact->id)
|
||||
->set('title', 'Neue Meldung fuer Presseecho')
|
||||
->set('text', str_repeat('Dies ist ein ausreichend langer Testtext. ', 3))
|
||||
->call('save', 'draft')
|
||||
|
|
|
|||
44
tests/Feature/LocalFontsTest.php
Normal file
44
tests/Feature/LocalFontsTest.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
test('views load fonts from local assets only', function () {
|
||||
$externalFontHosts = [
|
||||
'fonts.bunny.net',
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
];
|
||||
|
||||
foreach (File::allFiles(resource_path('views')) as $view) {
|
||||
$contents = File::get($view->getRealPath());
|
||||
|
||||
foreach ($externalFontHosts as $host) {
|
||||
$this->assertStringNotContainsString(
|
||||
$host,
|
||||
$contents,
|
||||
$view->getRelativePathname().' references '.$host,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$localFontLinks = view('partials.local-fonts')->render();
|
||||
|
||||
expect($localFontLinks)
|
||||
->toContain('fonts/inter-tight/font.css')
|
||||
->toContain('fonts/source-serif-4/font.css')
|
||||
->toContain('fonts/jetbrains-mono/font.css');
|
||||
});
|
||||
|
||||
test('local font stylesheets reference font files relative to their directories', function () {
|
||||
foreach (File::allFiles(public_path('fonts')) as $file) {
|
||||
if ($file->getExtension() !== 'css') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'../fonts/',
|
||||
File::get($file->getRealPath()),
|
||||
$file->getRelativePathname().' still points outside its font directory',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -58,6 +58,24 @@ test('customer can access me dashboard but not admin dashboard', function () {
|
|||
$this->get(route('dashboard'))->assertForbidden();
|
||||
});
|
||||
|
||||
test('customer bookings page shows credit packages and add ons from pricing concept', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get(route('me.bookings.index'))
|
||||
->assertSuccessful()
|
||||
->assertSee('Credit-Pakete')
|
||||
->assertSee('Standard')
|
||||
->assertSee('50')
|
||||
->assertSee('45 €')
|
||||
->assertSee('Pressetext-Optimierung')
|
||||
->assertSee('Top-Slot Startseite')
|
||||
->assertSee('Score 80+')
|
||||
->assertSee('Noch keine aktiven Buchungen');
|
||||
});
|
||||
|
||||
test('admin can access both panel dashboards', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
|
|
|
|||
219
tests/Feature/PressReleaseAttachmentsManagerTest.php
Normal file
219
tests/Feature/PressReleaseAttachmentsManagerTest.php
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\PressReleaseAttachment;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
function publicDisk(): FilesystemAdapter
|
||||
{
|
||||
/** @var FilesystemAdapter $disk */
|
||||
$disk = Storage::disk('public');
|
||||
|
||||
return $disk;
|
||||
}
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
Storage::fake('public');
|
||||
});
|
||||
|
||||
function makeDraftWithOwner(string $status = 'draft'): array
|
||||
{
|
||||
$owner = User::factory()->create(['is_active' => true]);
|
||||
$owner->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$owner->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'user_id' => $owner->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => Category::factory()->create()->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return compact('owner', 'company', 'pr');
|
||||
}
|
||||
|
||||
test('owner can upload a PDF attachment', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
$file = UploadedFile::fake()->create('factsheet.pdf', 200, 'application/pdf');
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $file)
|
||||
->set('newTitle', 'Factsheet Q1')
|
||||
->set('newDescription', 'Wirtschaftliche Kennzahlen')
|
||||
->call('upload')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$att = $pr->attachments()->first();
|
||||
|
||||
expect($att)->not->toBeNull();
|
||||
expect($att->title)->toBe('Factsheet Q1');
|
||||
expect($att->description)->toBe('Wirtschaftliche Kennzahlen');
|
||||
expect($att->original_name)->toBe('factsheet.pdf');
|
||||
expect($att->mime)->toBe('application/pdf');
|
||||
expect($att->sort_order)->toBe(1);
|
||||
expect($att->disk)->toBe('public');
|
||||
publicDisk()->assertExists($att->path);
|
||||
});
|
||||
|
||||
test('upload rejects executable / disallowed file types', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
$bad = UploadedFile::fake()->create('shell.exe', 50, 'application/octet-stream');
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $bad)
|
||||
->call('upload')
|
||||
->assertHasErrors(['newFile']);
|
||||
|
||||
expect($pr->attachments()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('upload rejects oversized files', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
// 26 MB (limit ist 25 MB).
|
||||
$tooBig = UploadedFile::fake()->create('big.pdf', 26 * 1024, 'application/pdf');
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $tooBig)
|
||||
->call('upload')
|
||||
->assertHasErrors(['newFile']);
|
||||
|
||||
expect($pr->attachments()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('sort_order increments and moveUp / moveDown swap order', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$a = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 1, 'title' => 'A']);
|
||||
$b = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 2, 'title' => 'B']);
|
||||
$c = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 3, 'title' => 'C']);
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->call('moveUp', $c->id);
|
||||
|
||||
expect($b->fresh()->sort_order)->toBe(3);
|
||||
expect($c->fresh()->sort_order)->toBe(2);
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->call('moveDown', $a->id);
|
||||
|
||||
// Nach moveDown sind a und c gleich (jeweils sort=2 nach swap). Es wird der Vorgänger getauscht — also a tauscht mit c (new neighbour).
|
||||
// Wir prüfen pragmatisch: a steht nicht mehr auf 1.
|
||||
expect($a->fresh()->sort_order)->not->toBe(1);
|
||||
});
|
||||
|
||||
test('remove deletes the row and the file', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
$file = UploadedFile::fake()->create('toDelete.pdf', 50, 'application/pdf');
|
||||
|
||||
$component = LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $file)
|
||||
->call('upload')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$att = $pr->attachments()->first();
|
||||
publicDisk()->assertExists($att->path);
|
||||
|
||||
$component->call('remove', $att->id);
|
||||
|
||||
expect($pr->attachments()->withTrashed()->find($att->id)?->trashed())->toBeTrue();
|
||||
publicDisk()->assertMissing($att->path);
|
||||
});
|
||||
|
||||
test('startEdit + updateAttachment update title and description', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner();
|
||||
$att = PressReleaseAttachment::factory()->for($pr)->create([
|
||||
'title' => 'Alt',
|
||||
'description' => 'Alte Beschreibung',
|
||||
]);
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->call('startEdit', $att->id)
|
||||
->assertSet('editingId', $att->id)
|
||||
->assertSet('editTitle', 'Alt')
|
||||
->set('editTitle', 'Neuer Titel')
|
||||
->set('editDescription', 'Frische Beschreibung')
|
||||
->call('updateAttachment')
|
||||
->assertHasNoErrors()
|
||||
->assertSet('editingId', null);
|
||||
|
||||
$att->refresh();
|
||||
expect($att->title)->toBe('Neuer Titel');
|
||||
expect($att->description)->toBe('Frische Beschreibung');
|
||||
});
|
||||
|
||||
test('foreign customer cannot upload attachments', function () {
|
||||
/** @var TestCase $this */
|
||||
['pr' => $pr] = makeDraftWithOwner();
|
||||
|
||||
$stranger = User::factory()->create(['is_active' => true]);
|
||||
$stranger->assignRole('customer');
|
||||
|
||||
$this->actingAs($stranger);
|
||||
|
||||
$file = UploadedFile::fake()->create('any.pdf', 30, 'application/pdf');
|
||||
|
||||
try {
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $file)
|
||||
->call('upload');
|
||||
} catch (AuthorizationException) {
|
||||
// Erwartet: Policy verhindert den Upload.
|
||||
}
|
||||
|
||||
expect($pr->attachments()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('attachments cannot be uploaded when PR is published', function () {
|
||||
/** @var TestCase $this */
|
||||
['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner('published');
|
||||
|
||||
$this->actingAs($owner);
|
||||
|
||||
$file = UploadedFile::fake()->create('any.pdf', 30, 'application/pdf');
|
||||
|
||||
try {
|
||||
LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id])
|
||||
->set('newFile', $file)
|
||||
->call('upload');
|
||||
} catch (AuthorizationException) {
|
||||
// Erwartet: Customer darf an published PR nichts ändern.
|
||||
}
|
||||
|
||||
expect($pr->attachments()->count())->toBe(0);
|
||||
});
|
||||
167
tests/Feature/PressReleaseContactWarningPhase8cTest.php
Normal file
167
tests/Feature/PressReleaseContactWarningPhase8cTest.php
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{customer: User, company: Company, contact: Contact, category: Category}
|
||||
*/
|
||||
function makeCustomerForContactWarning(): array
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
'phone' => '+49 30 1234567',
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
return compact('customer', 'company', 'contact', 'category');
|
||||
}
|
||||
|
||||
function makeAdminForContactWarning(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('customer create zeigt Warn-Box, wenn kein Pressekontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'company' => $company] = makeCustomerForContactWarning();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('contactId', null)
|
||||
->assertSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('customer create zeigt Warn-Box NICHT, wenn ein Kontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'company' => $company, 'contact' => $contact] = makeCustomerForContactWarning();
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('contactId', $contact->id)
|
||||
->assertDontSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('customer edit zeigt Warn-Box, wenn kein Pressekontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'company' => $company, 'category' => $category] = makeCustomerForContactWarning();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->set('contactId', null)
|
||||
->assertSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('customer edit zeigt Warn-Box NICHT, wenn ein Kontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'company' => $company, 'contact' => $contact, 'category' => $category] = makeCustomerForContactWarning();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
]);
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.edit', ['id' => $pr->id])
|
||||
->assertDontSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('admin create zeigt Warn-Box, wenn kein Pressekontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForContactWarning();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
'phone' => '+49 30 7654321',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.create')
|
||||
->set('companyId', $company->id)
|
||||
->set('contactId', null)
|
||||
->assertSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('admin edit zeigt Warn-Box, wenn kein Pressekontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForContactWarning();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
'phone' => '+49 30 7654321',
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->set('contactId', null)
|
||||
->assertSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
|
||||
test('admin edit zeigt Warn-Box NICHT, wenn ein Kontakt gewählt ist', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForContactWarning();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
'phone' => '+49 30 7654321',
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
]);
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pr->id])
|
||||
->assertDontSee('Noch kein Pressekontakt ausgewählt');
|
||||
});
|
||||
154
tests/Feature/PressReleaseIndexPhase8bTest.php
Normal file
154
tests/Feature/PressReleaseIndexPhase8bTest.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
function makeCustomerForIndexPhase8b(): User
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
function makeAdminForIndexPhase8b(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('customer list zeigt Scheduling-Sub-Zeile bei review-PMs mit scheduled_at', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForIndexPhase8b();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => now()->addDays(3)->setTime(14, 30),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.index')
|
||||
->assertSee('geplant');
|
||||
});
|
||||
|
||||
test('customer list zeigt Embargo-Sub-Zeile bei PMs mit embargo_at in der Zukunft', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForIndexPhase8b();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'embargo_at' => now()->addDays(7),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.index')
|
||||
->assertSee('Embargo bis');
|
||||
});
|
||||
|
||||
test('customer list zeigt KEINE Scheduling-Sub-Zeile wenn scheduled_at in der Vergangenheit', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForIndexPhase8b();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'scheduled_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.index')
|
||||
->assertDontSee('geplant ·');
|
||||
});
|
||||
|
||||
test('customer list zeigt KEINE Embargo-Sub-Zeile wenn embargo_at abgelaufen', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = makeCustomerForIndexPhase8b();
|
||||
$this->actingAs($customer);
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'embargo_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.index')
|
||||
->assertDontSee('Embargo bis');
|
||||
});
|
||||
|
||||
test('admin list zeigt Scheduling-Sub-Zeile bei review-PMs mit scheduled_at', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForIndexPhase8b();
|
||||
$this->actingAs($admin);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => now()->addDays(2)->setTime(10, 0),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.index')
|
||||
->assertSee('geplant');
|
||||
});
|
||||
|
||||
test('admin list zeigt Embargo-Sub-Zeile bei PMs mit embargo_at in der Zukunft', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForIndexPhase8b();
|
||||
$this->actingAs($admin);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'embargo_at' => now()->addDays(5),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.index')
|
||||
->assertSee('Embargo bis');
|
||||
});
|
||||
|
||||
test('admin list zeigt KEINE Sub-Zeilen wenn weder scheduled_at noch embargo_at', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForIndexPhase8b();
|
||||
$this->actingAs($admin);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
'scheduled_at' => null,
|
||||
'embargo_at' => null,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.index')
|
||||
->assertDontSee('geplant ·')
|
||||
->assertDontSee('Embargo bis');
|
||||
});
|
||||
193
tests/Feature/PressReleaseSchedulingTest.php
Normal file
193
tests/Feature/PressReleaseSchedulingTest.php
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
use App\Console\Commands\PublishScheduledPressReleases;
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\PressReleaseStatusLog;
|
||||
use App\Services\PressRelease\PressReleaseService;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
Mail::fake();
|
||||
});
|
||||
|
||||
test('publish ohne scheduled_at und ohne embargo_at setzt published_at auf jetzt', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => null,
|
||||
'embargo_at' => null,
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-01 10:00:00');
|
||||
});
|
||||
|
||||
test('publish mit scheduled_at in der Zukunft setzt published_at auf den Termin', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => null,
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-05 14:30:00');
|
||||
});
|
||||
|
||||
test('publish mit embargo_at in der Zukunft verschiebt published_at auf das Embargo', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => null,
|
||||
'embargo_at' => '2026-06-10 08:00:00',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-10 08:00:00');
|
||||
});
|
||||
|
||||
test('publish mit scheduled_at und späterem embargo_at nimmt das Embargo', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => '2026-06-10 08:00:00',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-10 08:00:00');
|
||||
});
|
||||
|
||||
test('publish übernimmt bereits gesetztes published_at und überschreibt nicht', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 10:00:00');
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-05 14:30:00',
|
||||
'embargo_at' => null,
|
||||
'published_at' => '2025-12-01 00:00:00',
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2025-12-01 00:00:00');
|
||||
});
|
||||
|
||||
test('publish-Source landet als source im Status-Log', function () {
|
||||
/** @var TestCase $this */
|
||||
$pr = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
app(PressReleaseService::class)->publish($pr, source: 'scheduler');
|
||||
|
||||
$log = PressReleaseStatusLog::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($log->source)->toBe('scheduler');
|
||||
expect($log->to_status)->toBe(PressReleaseStatus::Published);
|
||||
});
|
||||
|
||||
test('Command publisht fällige Review-PMs mit scheduled_at <= now', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 12:00:00');
|
||||
|
||||
$due = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-01 11:55:00',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
Artisan::call(PublishScheduledPressReleases::class);
|
||||
|
||||
$fresh = $due->fresh();
|
||||
expect($fresh->status)->toBe(PressReleaseStatus::Published);
|
||||
expect($fresh->published_at?->toDateTimeString())->toBe('2026-06-01 11:55:00');
|
||||
});
|
||||
|
||||
test('Command ignoriert PMs mit scheduled_at in der Zukunft', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 12:00:00');
|
||||
|
||||
$future = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-01 12:30:00',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
Artisan::call(PublishScheduledPressReleases::class);
|
||||
|
||||
expect($future->fresh()->status)->toBe(PressReleaseStatus::Review);
|
||||
});
|
||||
|
||||
test('Command ignoriert PMs ohne scheduled_at', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 12:00:00');
|
||||
|
||||
$manual = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => null,
|
||||
]);
|
||||
|
||||
Artisan::call(PublishScheduledPressReleases::class);
|
||||
|
||||
expect($manual->fresh()->status)->toBe(PressReleaseStatus::Review);
|
||||
});
|
||||
|
||||
test('Command läuft mit dry-run ohne Statusänderung', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 12:00:00');
|
||||
|
||||
$due = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-01 11:50:00',
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
Artisan::call(PublishScheduledPressReleases::class, ['--dry-run' => true]);
|
||||
|
||||
expect($due->fresh()->status)->toBe(PressReleaseStatus::Review);
|
||||
});
|
||||
|
||||
test('Command publisht maximal --limit pro Lauf', function () {
|
||||
/** @var TestCase $this */
|
||||
Carbon::setTestNow('2026-06-01 12:00:00');
|
||||
|
||||
PressRelease::factory()->count(3)->state([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-06-01 11:50:00',
|
||||
'published_at' => null,
|
||||
])->create();
|
||||
|
||||
Artisan::call(PublishScheduledPressReleases::class, ['--limit' => 2]);
|
||||
|
||||
$publishedCount = PressRelease::withoutGlobalScopes()
|
||||
->where('status', PressReleaseStatus::Published->value)
|
||||
->count();
|
||||
|
||||
expect($publishedCount)->toBe(2);
|
||||
});
|
||||
168
tests/Feature/PressReleaseShowPhase8aTest.php
Normal file
168
tests/Feature/PressReleaseShowPhase8aTest.php
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{customer: User, company: Company, contact: Contact, category: Category, pr: PressRelease}
|
||||
*/
|
||||
function makeCustomerForShowPhase8a(array $prAttributes = []): array
|
||||
{
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$contact = Contact::factory()->for($company)->create([
|
||||
'portal' => $company->portal->value,
|
||||
]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$pr = PressRelease::factory()->create(array_merge([
|
||||
'user_id' => $customer->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => $company->portal->value,
|
||||
'status' => 'draft',
|
||||
], $prAttributes));
|
||||
|
||||
$pr->contacts()->sync([$contact->id]);
|
||||
|
||||
return compact('customer', 'company', 'contact', 'category', 'pr');
|
||||
}
|
||||
|
||||
function makeAdminForShowPhase8a(): User
|
||||
{
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
test('customer show zeigt den Untertitel direkt unter dem Titel', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'title' => 'Brauerei eröffnet zweiten Standort',
|
||||
'subtitle' => 'Ein Untertitel der Pressemitteilung',
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Brauerei eröffnet zweiten Standort')
|
||||
->assertSee('Ein Untertitel der Pressemitteilung');
|
||||
});
|
||||
|
||||
test('customer show zeigt geplante Veröffentlichung wenn gesetzt', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'scheduled_at' => '2026-07-01 09:30:00',
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Geplante Veröffentlichung')
|
||||
->assertSee('01.07.2026 09:30');
|
||||
});
|
||||
|
||||
test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'status' => PressReleaseStatus::Published->value,
|
||||
'embargo_at' => '2026-08-15 12:00:00',
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Sperrfrist bis')
|
||||
->assertSee('15.08.2026 12:00');
|
||||
});
|
||||
|
||||
test('customer show zeigt Kein-Export-Hinweis wenn no_export aktiv', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'no_export' => true,
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Kein Export aktiv');
|
||||
});
|
||||
|
||||
test('customer show zeigt Boilerplate-Override als eigene Card', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'boilerplate_override' => 'Über die Beispiel AG: Wir machen Bier seit 1850.',
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Eigener Abbinder (Boilerplate)')
|
||||
->assertSee('Über die Beispiel AG: Wir machen Bier seit 1850.');
|
||||
});
|
||||
|
||||
test('customer show zeigt Boilerplate-Override nicht wenn leer', function () {
|
||||
/** @var TestCase $this */
|
||||
['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a([
|
||||
'boilerplate_override' => null,
|
||||
]);
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertDontSee('Eigener Abbinder (Boilerplate)');
|
||||
});
|
||||
|
||||
test('admin show zeigt den Untertitel direkt unter dem Titel', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShowPhase8a();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'title' => 'Großer Auftritt der Brauerei',
|
||||
'subtitle' => 'Pressekonferenz am Freitag',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Großer Auftritt der Brauerei')
|
||||
->assertSee('Pressekonferenz am Freitag');
|
||||
});
|
||||
|
||||
test('admin show zeigt Boilerplate-Override als eigene Card', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShowPhase8a();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'boilerplate_override' => 'Über das Beispiel-Unternehmen: Mehr Infos folgen.',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Eigener Abbinder (Boilerplate)')
|
||||
->assertSee('Über das Beispiel-Unternehmen: Mehr Infos folgen.');
|
||||
});
|
||||
|
||||
test('admin show zeigt Boilerplate-Override nicht wenn leer', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = makeAdminForShowPhase8a();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pr = PressRelease::factory()->create([
|
||||
'boilerplate_override' => null,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
|
||||
->assertDontSee('Eigener Abbinder (Boilerplate)');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue