12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
156
tests/Feature/Admin/AdminCategoryManagementTest.php
Normal file
156
tests/Feature/Admin/AdminCategoryManagementTest.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Models\Category;
|
||||
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);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
});
|
||||
|
||||
test('admin can render category create page', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->get(route('admin.categories.create'))
|
||||
->assertSuccessful()
|
||||
->assertSee('Kategorie anlegen');
|
||||
});
|
||||
|
||||
test('admin can create a new category with both translations', function () {
|
||||
LivewireVolt::test('admin.categories.create')
|
||||
->set('portal', Portal::Both->value)
|
||||
->set('nameDe', 'Künstliche Intelligenz')
|
||||
->set('nameEn', 'Artificial Intelligence')
|
||||
->set('descriptionDe', 'Themen rund um KI.')
|
||||
->set('descriptionEn', 'Topics around AI.')
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.categories.index'));
|
||||
|
||||
$category = Category::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($category->portal)->toBe(Portal::Both);
|
||||
expect($category->is_active)->toBeTrue();
|
||||
expect($category->translations()->count())->toBe(2);
|
||||
|
||||
$de = $category->translations->firstWhere('locale', 'de');
|
||||
$en = $category->translations->firstWhere('locale', 'en');
|
||||
|
||||
expect($de->name)->toBe('Künstliche Intelligenz');
|
||||
expect($de->slug)->toBe('kunstliche-intelligenz');
|
||||
expect($en->slug)->toBe('artificial-intelligence');
|
||||
});
|
||||
|
||||
test('category create requires both names', function () {
|
||||
LivewireVolt::test('admin.categories.create')
|
||||
->set('nameDe', '')
|
||||
->set('nameEn', '')
|
||||
->call('save')
|
||||
->assertHasErrors(['nameDe', 'nameEn']);
|
||||
});
|
||||
|
||||
test('category create generates unique slug per locale on collision', function () {
|
||||
$existing = Category::factory()->create(['portal' => Portal::Both->value]);
|
||||
$existing->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Wirtschaft', 'slug' => 'wirtschaft'],
|
||||
['locale' => 'en', 'name' => 'Business', 'slug' => 'business'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.create')
|
||||
->set('nameDe', 'Wirtschaft')
|
||||
->set('nameEn', 'Business')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$newCategory = Category::query()->latest('id')->firstOrFail();
|
||||
$de = $newCategory->translations->firstWhere('locale', 'de');
|
||||
$en = $newCategory->translations->firstWhere('locale', 'en');
|
||||
|
||||
expect($de->slug)->toBe('wirtschaft-2');
|
||||
expect($en->slug)->toBe('business-2');
|
||||
});
|
||||
|
||||
test('admin can edit an existing category and rename slugs in place', function () {
|
||||
$category = Category::factory()->create(['portal' => Portal::Both->value]);
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Alter Name', 'slug' => 'alter-name'],
|
||||
['locale' => 'en', 'name' => 'Old Name', 'slug' => 'old-name'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.edit', ['id' => $category->id])
|
||||
->set('nameDe', 'Neuer Name')
|
||||
->set('slugDe', 'neuer-name')
|
||||
->set('nameEn', 'New Name')
|
||||
->set('slugEn', 'new-name')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$category->refresh()->load('translations');
|
||||
$de = $category->translations->firstWhere('locale', 'de');
|
||||
$en = $category->translations->firstWhere('locale', 'en');
|
||||
|
||||
expect($de->slug)->toBe('neuer-name');
|
||||
expect($en->slug)->toBe('new-name');
|
||||
});
|
||||
|
||||
test('category edit refuses parent assignment that would create a hierarchy loop', function () {
|
||||
$parent = Category::factory()->create();
|
||||
$parent->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Parent', 'slug' => 'parent'],
|
||||
['locale' => 'en', 'name' => 'Parent', 'slug' => 'parent-en'],
|
||||
]);
|
||||
|
||||
$child = Category::factory()->create(['parent_id' => $parent->id]);
|
||||
$child->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Child', 'slug' => 'child'],
|
||||
['locale' => 'en', 'name' => 'Child', 'slug' => 'child-en'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.edit', ['id' => $parent->id])
|
||||
->set('nameDe', 'Parent')
|
||||
->set('slugDe', 'parent')
|
||||
->set('nameEn', 'Parent')
|
||||
->set('slugEn', 'parent-en')
|
||||
->set('parentId', $child->id)
|
||||
->call('save')
|
||||
->assertHasErrors(['parentId']);
|
||||
});
|
||||
|
||||
test('category cannot be deleted while it has press releases', function () {
|
||||
$category = Category::factory()->create();
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Used', 'slug' => 'used'],
|
||||
['locale' => 'en', 'name' => 'Used', 'slug' => 'used-en'],
|
||||
]);
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'category_id' => $category->id,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.edit', ['id' => $category->id])
|
||||
->call('deleteCategory');
|
||||
|
||||
expect(Category::query()->find($category->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
test('category can be deleted when empty', function () {
|
||||
$category = Category::factory()->create();
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Empty', 'slug' => 'empty'],
|
||||
['locale' => 'en', 'name' => 'Empty', 'slug' => 'empty-en'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.edit', ['id' => $category->id])
|
||||
->call('deleteCategory')
|
||||
->assertRedirect(route('admin.categories.index'));
|
||||
|
||||
expect(Category::query()->find($category->id))->toBeNull();
|
||||
});
|
||||
162
tests/Feature/Admin/AdminFooterCodeManagementTest.php
Normal file
162
tests/Feature/Admin/AdminFooterCodeManagementTest.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Models\Category;
|
||||
use App\Models\FooterCode;
|
||||
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);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
});
|
||||
|
||||
test('admin can render footer-codes index', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->get(route('admin.footer-codes.index'))
|
||||
->assertSuccessful()
|
||||
->assertSee('Footer-Codes');
|
||||
});
|
||||
|
||||
test('admin can render footer-code create page', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->get(route('admin.footer-codes.create'))
|
||||
->assertSuccessful()
|
||||
->assertSee('Footer-Code anlegen');
|
||||
});
|
||||
|
||||
test('admin can create a category-bound footer code', function () {
|
||||
$category = Category::factory()->create();
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Wirtschaft', 'slug' => 'wirtschaft'],
|
||||
['locale' => 'en', 'name' => 'Business', 'slug' => 'business'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.create')
|
||||
->set('title', 'Wirtschafts-Disclaimer')
|
||||
->set('content', '<p>Powered by Presseportale</p>')
|
||||
->set('portal', Portal::Both->value)
|
||||
->set('language', 'de')
|
||||
->set('priority', 10)
|
||||
->set('isGlobal', false)
|
||||
->set('categoryIds', [$category->id])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.footer-codes.index'));
|
||||
|
||||
$code = FooterCode::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($code->title)->toBe('Wirtschafts-Disclaimer');
|
||||
expect($code->is_global)->toBeFalse();
|
||||
expect($code->is_active)->toBeTrue();
|
||||
expect($code->categories->pluck('id')->all())->toBe([$category->id]);
|
||||
});
|
||||
|
||||
test('global footer code ignores category assignment', function () {
|
||||
$category = Category::factory()->create();
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Tech', 'slug' => 'tech'],
|
||||
['locale' => 'en', 'name' => 'Tech', 'slug' => 'tech-en'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.create')
|
||||
->set('title', 'Globaler Disclaimer')
|
||||
->set('content', '<p>Hinweis</p>')
|
||||
->set('isGlobal', true)
|
||||
->set('categoryIds', [$category->id])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$code = FooterCode::query()->latest('id')->firstOrFail();
|
||||
|
||||
expect($code->is_global)->toBeTrue();
|
||||
expect($code->categories()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('footer code create requires title and content', function () {
|
||||
LivewireVolt::test('admin.footer-codes.create')
|
||||
->set('title', '')
|
||||
->set('content', '')
|
||||
->call('save')
|
||||
->assertHasErrors(['title', 'content']);
|
||||
});
|
||||
|
||||
test('admin can edit and reassign categories of a footer code', function () {
|
||||
$code = FooterCode::factory()->create([
|
||||
'title' => 'Alt',
|
||||
'is_global' => false,
|
||||
]);
|
||||
|
||||
$oldCategory = Category::factory()->create();
|
||||
$oldCategory->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Alt', 'slug' => 'alt-cat'],
|
||||
['locale' => 'en', 'name' => 'Old', 'slug' => 'old-cat'],
|
||||
]);
|
||||
$code->categories()->sync([$oldCategory->id]);
|
||||
|
||||
$newCategory = Category::factory()->create();
|
||||
$newCategory->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Neu', 'slug' => 'neu-cat'],
|
||||
['locale' => 'en', 'name' => 'New', 'slug' => 'new-cat'],
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.edit', ['id' => $code->id])
|
||||
->set('title', 'Neu')
|
||||
->set('content', $code->content)
|
||||
->set('categoryIds', [$newCategory->id])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$code->refresh();
|
||||
|
||||
expect($code->title)->toBe('Neu');
|
||||
expect($code->categories->pluck('id')->all())->toBe([$newCategory->id]);
|
||||
});
|
||||
|
||||
test('toggling global removes category links on save', function () {
|
||||
$code = FooterCode::factory()->create(['is_global' => false]);
|
||||
|
||||
$category = Category::factory()->create();
|
||||
$category->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'X', 'slug' => 'x-cat'],
|
||||
['locale' => 'en', 'name' => 'X', 'slug' => 'x-cat-en'],
|
||||
]);
|
||||
$code->categories()->sync([$category->id]);
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.edit', ['id' => $code->id])
|
||||
->set('content', $code->content)
|
||||
->set('isGlobal', true)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$code->refresh();
|
||||
|
||||
expect($code->is_global)->toBeTrue();
|
||||
expect($code->categories()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('admin can soft-delete a footer code', function () {
|
||||
$code = FooterCode::factory()->create();
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.edit', ['id' => $code->id])
|
||||
->call('delete')
|
||||
->assertRedirect(route('admin.footer-codes.index'));
|
||||
|
||||
expect(FooterCode::query()->find($code->id))->toBeNull();
|
||||
expect(FooterCode::withTrashed()->find($code->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
test('admin can toggle footer code activation from index', function () {
|
||||
$code = FooterCode::factory()->create(['is_active' => true]);
|
||||
|
||||
LivewireVolt::test('admin.footer-codes.index')
|
||||
->call('toggleActive', $code->id);
|
||||
|
||||
expect($code->fresh()->is_active)->toBeFalse();
|
||||
});
|
||||
111
tests/Feature/Admin/AdminLegacyInvoiceArchiveTest.php
Normal file
111
tests/Feature/Admin/AdminLegacyInvoiceArchiveTest.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
use App\Models\LegacyInvoice;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$this->actingAs($admin);
|
||||
});
|
||||
|
||||
test('admin sees legacy invoice archive overview with filters and mapping hints', function () {
|
||||
$customer = User::factory()->create([
|
||||
'is_active' => true,
|
||||
'name' => 'Archiv Kunde',
|
||||
'email' => 'archiv@example.com',
|
||||
]);
|
||||
|
||||
LegacyInvoice::query()->create([
|
||||
'legacy_portal' => 'presseecho',
|
||||
'legacy_id' => 2001,
|
||||
'user_id' => $customer->id,
|
||||
'legacy_user_id' => 501,
|
||||
'number' => 'PE-2001',
|
||||
'amount_cents' => 4900,
|
||||
'total_cents' => 4900,
|
||||
'status' => 'paid',
|
||||
'invoice_date' => now()->subMonth(),
|
||||
'paid_at' => now()->subMonth()->addDays(2),
|
||||
'imported_at' => now(),
|
||||
]);
|
||||
|
||||
LegacyInvoice::query()->create([
|
||||
'legacy_portal' => 'businessportal24',
|
||||
'legacy_id' => 2002,
|
||||
'user_id' => null,
|
||||
'legacy_user_id' => 999,
|
||||
'number' => 'BP-2002',
|
||||
'amount_cents' => 11900,
|
||||
'total_cents' => 11900,
|
||||
'status' => 'open',
|
||||
'invoice_date' => now()->subWeeks(2),
|
||||
'imported_at' => now(),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.invoices.index')
|
||||
->assertSee('Legacy-Rechnungsarchiv')
|
||||
->assertSee('PE-2001')
|
||||
->assertSee('BP-2002')
|
||||
->assertSee('Ohne Zuordnung')
|
||||
->assertSee('Archiv Kunde')
|
||||
->set('mappingFilter', 'unmapped')
|
||||
->assertSee('BP-2002')
|
||||
->assertDontSee('PE-2001')
|
||||
->set('mappingFilter', 'all')
|
||||
->set('portalFilter', 'presseecho')
|
||||
->assertSee('PE-2001')
|
||||
->assertDontSee('BP-2002')
|
||||
->call('resetFilters')
|
||||
->assertSee('PE-2001')
|
||||
->assertSee('BP-2002');
|
||||
});
|
||||
|
||||
test('admin opens generated legacy invoice pdf inline', function () {
|
||||
/** @var TestCase $this */
|
||||
$invoice = LegacyInvoice::query()->create([
|
||||
'legacy_portal' => 'presseecho',
|
||||
'legacy_id' => 2003,
|
||||
'user_id' => null,
|
||||
'legacy_user_id' => 503,
|
||||
'number' => 'PE-2003',
|
||||
'amount_cents' => 9900,
|
||||
'total_cents' => 9900,
|
||||
'status' => 'paid',
|
||||
'invoice_date' => now()->subMonth(),
|
||||
'paid_at' => now()->subMonth()->addDays(2),
|
||||
'payment_method' => 'SPK_Berlin',
|
||||
'raw_snapshot' => [
|
||||
'number' => 'PE-2003',
|
||||
'service_period_begin_date' => now()->subYear()->toDateString(),
|
||||
'service_period_end_date' => now()->toDateString(),
|
||||
],
|
||||
'pdf_payload' => [
|
||||
'billing_address' => [
|
||||
'name' => 'Admin Archiv GmbH',
|
||||
'address' => 'Archivstrasse 1',
|
||||
'postal_code' => '10115',
|
||||
'city' => 'Berlin',
|
||||
'country_name' => 'Deutschland',
|
||||
],
|
||||
],
|
||||
'imported_at' => now(),
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.invoices.index')
|
||||
->assertSee('Öffnen');
|
||||
|
||||
$this->get(route('admin.legacy-invoices.pdf', $invoice))
|
||||
->assertSuccessful()
|
||||
->assertHeader('content-type', 'application/pdf')
|
||||
->assertHeader('content-disposition', 'inline; filename="Presseecho-RNr-PE-2003.pdf"');
|
||||
|
||||
expect($invoice->refresh()->pdf_generated_at)->not->toBeNull();
|
||||
});
|
||||
29
tests/Feature/Admin/AdminPerformanceCacheTest.php
Normal file
29
tests/Feature/Admin/AdminPerformanceCacheTest.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
test('admin performance cache is flushed when observed models change', function () {
|
||||
Cache::put(AdminPerformanceCache::DashboardStats, ['stale' => true], 600);
|
||||
Cache::put(AdminPerformanceCache::PressReleaseStats, ['stale' => true], 600);
|
||||
Cache::put(AdminPerformanceCache::RoleOptions, ['stale' => true], 600);
|
||||
|
||||
Company::factory()->create();
|
||||
|
||||
expect(Cache::has(AdminPerformanceCache::DashboardStats))->toBeFalse()
|
||||
->and(Cache::has(AdminPerformanceCache::PressReleaseStats))->toBeFalse()
|
||||
->and(Cache::has(AdminPerformanceCache::RoleOptions))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin role option cache is flushed when roles change', function () {
|
||||
Cache::put(AdminPerformanceCache::RoleOptions, ['stale' => true], 600);
|
||||
|
||||
Role::query()->create([
|
||||
'name' => 'temporary-admin-role',
|
||||
'guard_name' => 'web',
|
||||
]);
|
||||
|
||||
expect(Cache::has(AdminPerformanceCache::RoleOptions))->toBeFalse();
|
||||
});
|
||||
78
tests/Feature/Admin/AdminPresetManagementTest.php
Normal file
78
tests/Feature/Admin/AdminPresetManagementTest.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
use App\Models\AdminPreset;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('admin presets are linked in navigation', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.presets.index'))
|
||||
->assertSuccessful()
|
||||
->assertSee('Voreinstellungen');
|
||||
});
|
||||
|
||||
test('admin can create a preset', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.presets.create')
|
||||
->set('key', 'press_releases.test_text')
|
||||
->set('area', 'press_releases')
|
||||
->set('type', 'text')
|
||||
->set('label', 'Test Text')
|
||||
->set('value', 'Ein Preset Text')
|
||||
->set('payload', '{"source":"test"}')
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.presets.index'));
|
||||
|
||||
$this->assertDatabaseHas('admin_presets', [
|
||||
'key' => 'press_releases.test_text',
|
||||
'area' => 'press_releases',
|
||||
'type' => 'text',
|
||||
'label' => 'Test Text',
|
||||
'value' => 'Ein Preset Text',
|
||||
]);
|
||||
});
|
||||
|
||||
test('admin can edit a preset', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$preset = AdminPreset::factory()->create([
|
||||
'key' => 'press_releases.edit_text',
|
||||
'area' => 'press_releases',
|
||||
'label' => 'Alter Text',
|
||||
'value' => 'Alt',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.presets.edit', ['id' => $preset->id])
|
||||
->set('label', 'Neuer Text')
|
||||
->set('value', 'Neu')
|
||||
->set('isActive', false)
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.presets.index'));
|
||||
|
||||
$preset->refresh();
|
||||
|
||||
expect($preset->label)->toBe('Neuer Text')
|
||||
->and($preset->value)->toBe('Neu')
|
||||
->and($preset->is_active)->toBeFalse();
|
||||
});
|
||||
150
tests/Feature/Admin/AdminPressReleaseActionsTest.php
Normal file
150
tests/Feature/Admin/AdminPressReleaseActionsTest.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\AdminPreset;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('published press releases are tombstoned instead of soft deleted', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$replacementText = 'Diese Meldung wurde redaktionell entfernt.';
|
||||
|
||||
AdminPreset::factory()->create([
|
||||
'key' => AdminPreset::PRESS_RELEASE_DELETED_PUBLISHED_TEXT,
|
||||
'area' => 'press_releases',
|
||||
'type' => 'text',
|
||||
'label' => 'Ersatztext',
|
||||
'value' => $replacementText,
|
||||
]);
|
||||
|
||||
$pressRelease = PressRelease::factory()
|
||||
->published()
|
||||
->create(['text' => 'Alter veröffentlichter Inhalt.']);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pressRelease->id])
|
||||
->call('deletePressRelease')
|
||||
->assertRedirect(route('admin.press-releases.index'));
|
||||
|
||||
$pressRelease->refresh();
|
||||
|
||||
expect($pressRelease->deleted_at)->toBeNull()
|
||||
->and($pressRelease->status)->toBe(PressReleaseStatus::Archived)
|
||||
->and($pressRelease->text)->toBe($replacementText)
|
||||
->and($pressRelease->no_export)->toBeTrue();
|
||||
});
|
||||
|
||||
test('unpublished press releases are soft deleted by admin delete action', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pressRelease = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Draft->value,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pressRelease->id])
|
||||
->call('deletePressRelease')
|
||||
->assertRedirect(route('admin.press-releases.index'));
|
||||
|
||||
$this->assertSoftDeleted($pressRelease);
|
||||
});
|
||||
|
||||
test('edit page renders status selector and confirmation modal', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pressRelease = PressRelease::factory()
|
||||
->inReview()
|
||||
->create();
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pressRelease->id])
|
||||
->assertSee('Neuer Status')
|
||||
->assertSee('Status wechseln')
|
||||
->assertSee('Status wirklich wechseln?')
|
||||
->assertSee('Pressemitteilung löschen?');
|
||||
});
|
||||
|
||||
test('admin can change archived press release back to another status', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$pressRelease = PressRelease::factory()->create([
|
||||
'status' => PressReleaseStatus::Archived->value,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.edit', ['id' => $pressRelease->id])
|
||||
->set('targetStatus', PressReleaseStatus::Draft->value)
|
||||
->call('changeStatus')
|
||||
->assertHasNoErrors()
|
||||
->assertSee('Status wurde auf');
|
||||
|
||||
$pressRelease->refresh();
|
||||
|
||||
expect($pressRelease->status)->toBe(PressReleaseStatus::Draft);
|
||||
});
|
||||
|
||||
test('press release index renders confirmation modals for status actions', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
PressRelease::factory()
|
||||
->inReview()
|
||||
->create(['title' => 'PM in Prüfung']);
|
||||
|
||||
PressRelease::factory()
|
||||
->published()
|
||||
->create(['title' => 'Veröffentlichte PM']);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.index')
|
||||
->assertSee('Pressemitteilung veröffentlichen?')
|
||||
->assertSee('Pressemitteilung ablehnen?')
|
||||
->assertSee('Pressemitteilung archivieren?');
|
||||
});
|
||||
|
||||
test('show page renders confirmation modals for status actions', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$reviewPressRelease = PressRelease::factory()
|
||||
->inReview()
|
||||
->create();
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $reviewPressRelease->id])
|
||||
->assertSee('Pressemitteilung veröffentlichen?')
|
||||
->assertSee('Pressemitteilung ablehnen?');
|
||||
|
||||
$publishedPressRelease = PressRelease::factory()
|
||||
->published()
|
||||
->create();
|
||||
|
||||
LivewireVolt::test('admin.press-releases.show', ['id' => $publishedPressRelease->id])
|
||||
->assertSee('Pressemitteilung archivieren?');
|
||||
});
|
||||
81
tests/Feature/Admin/AdminSlowRequestLoggingTest.php
Normal file
81
tests/Feature/Admin/AdminSlowRequestLoggingTest.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Middleware\EnsureUserIsAdmin;
|
||||
use App\Http\Middleware\LogSlowAdminRequests;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Livewire;
|
||||
use Mockery as MockeryFacade;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('slow admin requests are logged with request and query metrics', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
config([
|
||||
'admin_performance.slow_requests.enabled' => true,
|
||||
'admin_performance.slow_requests.duration_threshold_ms' => 0,
|
||||
'admin_performance.slow_requests.database_threshold_ms' => 0,
|
||||
'admin_performance.slow_requests.query_count_threshold' => 0,
|
||||
'admin_performance.slow_requests.slow_query_threshold_ms' => 0,
|
||||
'admin_performance.slow_requests.channel' => 'admin-slow-test',
|
||||
]);
|
||||
|
||||
$logger = MockeryFacade::mock(LoggerInterface::class);
|
||||
$logger->shouldReceive('warning')
|
||||
->once()
|
||||
->with('Slow admin request detected.', MockeryFacade::on(function (array $context): bool {
|
||||
return $context['method'] === 'GET'
|
||||
&& $context['path'] === '/admin/roles'
|
||||
&& $context['route_name'] === 'admin.roles.index'
|
||||
&& $context['status_code'] === 200
|
||||
&& is_int($context['user_id'])
|
||||
&& is_int($context['duration_ms'])
|
||||
&& is_float($context['database_time_ms'])
|
||||
&& is_int($context['query_count'])
|
||||
&& is_array($context['slow_queries']);
|
||||
}));
|
||||
|
||||
Log::shouldReceive('channel')
|
||||
->once()
|
||||
->with('admin-slow-test')
|
||||
->andReturn($logger);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.roles.index'))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('slow admin requests use the dedicated admin log channel by default', function () {
|
||||
expect(config('admin_performance.slow_requests.channel'))->toBe('admin_slow')
|
||||
->and(config('logging.channels.admin_slow.driver'))->toBe('daily')
|
||||
->and(config('logging.channels.admin_slow.path'))->toBe(storage_path('logs/admin-slow.log'))
|
||||
->and(config('logging.channels.admin_slow.level'))->toBe('warning');
|
||||
});
|
||||
|
||||
test('slow admin request logging can be disabled', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
config(['admin_performance.slow_requests.enabled' => false]);
|
||||
|
||||
Log::shouldReceive('channel')->never();
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.roles.index'))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin middleware is persisted for livewire follow-up requests', function () {
|
||||
expect(Livewire::getPersistentMiddleware())
|
||||
->toContain(EnsureUserIsAdmin::class)
|
||||
->toContain(LogSlowAdminRequests::class);
|
||||
});
|
||||
111
tests/Feature/Admin/AdminSlowRequestReportTest.php
Normal file
111
tests/Feature/Admin/AdminSlowRequestReportTest.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\Admin\AdminSlowRequestReporter;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('slow admin reporter parses and aggregates slow request logs', function () {
|
||||
$logPath = createAdminSlowRequestLog();
|
||||
|
||||
$report = app(AdminSlowRequestReporter::class)->report(
|
||||
filters: ['route' => 'admin.users'],
|
||||
top: 5,
|
||||
limit: 5,
|
||||
paths: [$logPath],
|
||||
);
|
||||
|
||||
expect($report['summary']['total_requests'])->toBe(2)
|
||||
->and($report['summary']['unique_routes'])->toBe(1)
|
||||
->and($report['summary']['max_duration_ms'])->toBe(1400)
|
||||
->and($report['top_routes'][0]['value'])->toBe('admin.users.index')
|
||||
->and($report['top_routes'][0]['requests'])->toBe(2)
|
||||
->and($report['slowest_requests'][0]['duration_ms'])->toBe(1400)
|
||||
->and($report['slow_queries'][0]['occurrences'])->toBe(2)
|
||||
->and($report['explain_plans'][0]['explainable'])->toBeTrue()
|
||||
->and($report['explain_plans'][0]['plan'])->not->toBeEmpty();
|
||||
});
|
||||
|
||||
test('slow admin request command renders report sections', function () {
|
||||
/** @var TestCase $this */
|
||||
$logPath = createAdminSlowRequestLog();
|
||||
|
||||
$this->artisan('admin:slow-requests', [
|
||||
'--file' => [$logPath],
|
||||
'--top' => 5,
|
||||
'--limit' => 5,
|
||||
])
|
||||
->assertSuccessful()
|
||||
->expectsOutput('Slow-Admin-Request-Report')
|
||||
->expectsOutput('Requests: 3')
|
||||
->expectsOutputToContain('Top Routen')
|
||||
->expectsOutputToContain('Langsamste Requests')
|
||||
->expectsOutputToContain('EXPLAIN Top Slow Queries');
|
||||
});
|
||||
|
||||
test('admin slow request report page renders filters and report data', function () {
|
||||
/** @var TestCase $this */
|
||||
$logPath = createAdminSlowRequestLog();
|
||||
config(['logging.channels.admin_slow.path' => $logPath]);
|
||||
config(['admin_performance.slow_requests.enabled' => false]);
|
||||
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.reports.slow-requests')
|
||||
->assertSee('Performance Reports')
|
||||
->assertSee('Top Routen')
|
||||
->assertSee('EXPLAIN Top Slow Queries')
|
||||
->assertSee('admin.users.index')
|
||||
->set('routeFilter', 'admin.contacts')
|
||||
->assertSee('admin.contacts.index')
|
||||
->assertDontSee('admin.users.index');
|
||||
});
|
||||
|
||||
function createAdminSlowRequestLog(): string
|
||||
{
|
||||
$path = storage_path('logs/testing-admin-slow.log');
|
||||
File::ensureDirectoryExists(dirname($path));
|
||||
File::put($path, collect([
|
||||
adminSlowRequestLogLine('2026-04-29 10:00:00', 'admin.users.index', '/admin/users', 920, 180.5, 42),
|
||||
adminSlowRequestLogLine('2026-04-29 10:05:00', 'admin.users.index', '/admin/users?page=2', 1400, 220.75, 58),
|
||||
adminSlowRequestLogLine('2026-04-29 10:10:00', 'admin.contacts.index', '/admin/contacts', 760, 90.25, 25, 302),
|
||||
])->implode(PHP_EOL).PHP_EOL);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
function adminSlowRequestLogLine(
|
||||
string $timestamp,
|
||||
string $route,
|
||||
string $path,
|
||||
int $durationMs,
|
||||
float $databaseTimeMs,
|
||||
int $queryCount,
|
||||
int $statusCode = 200,
|
||||
): string {
|
||||
$context = [
|
||||
'method' => 'GET',
|
||||
'path' => $path,
|
||||
'route_name' => $route,
|
||||
'status_code' => $statusCode,
|
||||
'user_id' => 1,
|
||||
'duration_ms' => $durationMs,
|
||||
'database_time_ms' => $databaseTimeMs,
|
||||
'query_count' => $queryCount,
|
||||
'slow_queries' => [
|
||||
[
|
||||
'sql' => 'select * from "users" where "email" = ?',
|
||||
'time_ms' => 75.5,
|
||||
'connection' => 'sqlite',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return '['.$timestamp.'] local.WARNING: Slow admin request detected. '.json_encode($context, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
106
tests/Feature/Admin/CategoryIndexPerformanceTest.php
Normal file
106
tests/Feature/Admin/CategoryIndexPerformanceTest.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Models\Category;
|
||||
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;
|
||||
|
||||
test('category index loads release counts without hydrating press release models', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$company = Company::factory()->create();
|
||||
$category = Category::factory()->withTranslations()->create();
|
||||
|
||||
PressRelease::factory()
|
||||
->count(2)
|
||||
->create([
|
||||
'user_id' => $admin->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => Portal::Presseecho->value,
|
||||
]);
|
||||
|
||||
PressRelease::factory()
|
||||
->create([
|
||||
'user_id' => $admin->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $category->id,
|
||||
'portal' => Portal::Businessportal24->value,
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.categories.index')
|
||||
->assertSee(route('admin.press-releases.index', ['category' => $category->id]), false)
|
||||
->assertViewHas('stats', fn (array $stats): bool => $stats['with_releases'] === 1
|
||||
&& $stats['total_releases'] === 3
|
||||
&& $stats['presseecho_releases'] === 2
|
||||
&& $stats['businessportal24_releases'] === 1)
|
||||
->assertViewHas('categories', function ($categories): bool {
|
||||
$firstCategory = $categories->first();
|
||||
|
||||
return $firstCategory instanceof Category
|
||||
&& $firstCategory->press_releases_count === 3
|
||||
&& $firstCategory->presseecho_press_releases_count === 2
|
||||
&& $firstCategory->businessportal24_press_releases_count === 1
|
||||
&& $firstCategory->relationLoaded('translations')
|
||||
&& ! $firstCategory->relationLoaded('pressReleases');
|
||||
});
|
||||
});
|
||||
|
||||
test('press release index can be filtered by category and shows category column', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$longCompanyName = 'Eine sehr lange Firma mit einem Namen der in der Tabelle begrenzt werden muss GmbH';
|
||||
|
||||
$company = Company::factory()->create([
|
||||
'name' => $longCompanyName,
|
||||
]);
|
||||
$selectedCategory = Category::factory()->create();
|
||||
$otherCategory = Category::factory()->create();
|
||||
|
||||
$selectedCategory->translations()->createMany([
|
||||
['locale' => 'de', 'name' => 'Wirtschaft', 'slug' => 'wirtschaft'],
|
||||
['locale' => 'en', 'name' => 'Business', 'slug' => 'business'],
|
||||
]);
|
||||
$otherCategory->translations()->create([
|
||||
'locale' => 'de',
|
||||
'name' => 'Technologie',
|
||||
'slug' => 'technologie',
|
||||
]);
|
||||
|
||||
$longTitle = 'Gefilterte Pressemitteilung mit einem sehr langen Titel der in der Tabelle ebenfalls begrenzt werden muss';
|
||||
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $admin->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $selectedCategory->id,
|
||||
'title' => $longTitle,
|
||||
]);
|
||||
PressRelease::factory()->create([
|
||||
'user_id' => $admin->id,
|
||||
'company_id' => $company->id,
|
||||
'category_id' => $otherCategory->id,
|
||||
'title' => 'Andere Pressemitteilung',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.press-releases.index')
|
||||
->set('categoryFilter', (string) $selectedCategory->id)
|
||||
->assertSee('Kategorie')
|
||||
->assertSee('Wirtschaft')
|
||||
->assertSee($longTitle)
|
||||
->assertSee($longCompanyName)
|
||||
->assertDontSee('Andere Pressemitteilung');
|
||||
});
|
||||
14
tests/Feature/Admin/PortalAssetManifestTest.php
Normal file
14
tests/Feature/Admin/PortalAssetManifestTest.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('admin portal page renders with the portal vite manifest', function () {
|
||||
/** @var TestCase $this */
|
||||
$admin = User::factory()->superAdmin()->create();
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get('https://presseportale.test/admin/companies')
|
||||
->assertSuccessful()
|
||||
->assertSee('Firmen');
|
||||
});
|
||||
52
tests/Feature/Admin/RoleManagementTest.php
Normal file
52
tests/Feature/Admin/RoleManagementTest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('admin can create a role with permissions', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.roles.create')
|
||||
->set('name', 'support')
|
||||
->set('permissions', ['users:manage', 'roles:manage'])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.roles.index'));
|
||||
|
||||
$role = Role::findByName('support');
|
||||
|
||||
expect($role->hasPermissionTo('users:manage'))->toBeTrue()
|
||||
->and($role->hasPermissionTo('roles:manage'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can edit a role with cached permission options', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->assignRole('admin');
|
||||
$this->actingAs($admin);
|
||||
|
||||
$role = Role::query()->create([
|
||||
'name' => 'reviewer',
|
||||
'guard_name' => 'web',
|
||||
]);
|
||||
|
||||
LivewireVolt::test('admin.roles.edit', ['id' => $role->id])
|
||||
->assertSee('roles:manage')
|
||||
->set('name', 'review-lead')
|
||||
->set('permissions', ['press-releases:read'])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('admin.roles.index'));
|
||||
|
||||
expect(Role::findByName('review-lead')->hasPermissionTo('press-releases:read'))->toBeTrue();
|
||||
});
|
||||
132
tests/Feature/Admin/UserImpersonationTest.php
Normal file
132
tests/Feature/Admin/UserImpersonationTest.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Admin\UserImpersonation;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('admin can impersonate a customer from user management', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$customer = User::factory()->create([
|
||||
'name' => 'Test Kunde',
|
||||
'email' => 'kunde@example.com',
|
||||
]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->assertSee('Login als User')
|
||||
->call('loginAsUser', $customer->id)
|
||||
->assertRedirect(route('me.dashboard'));
|
||||
|
||||
$this->assertAuthenticatedAs($customer);
|
||||
expect(session(UserImpersonation::SessionKey))->toBe($admin->id);
|
||||
|
||||
$this->get(route('dashboard'))->assertRedirect(route('me.dashboard'));
|
||||
$this->get(route('me.dashboard'))->assertSuccessful();
|
||||
});
|
||||
|
||||
test('users without manage permission cannot start impersonation', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$editor = User::factory()->create();
|
||||
$editor->assignRole('editor');
|
||||
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->actingAs($editor);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->assertDontSee('Login als User')
|
||||
->call('loginAsUser', $customer->id)
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertAuthenticatedAs($editor);
|
||||
$this->assertFalse(session()->has(UserImpersonation::SessionKey));
|
||||
});
|
||||
|
||||
test('admin can impersonate accounts with admin access', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$targetAdmin = User::factory()->create([
|
||||
'email' => 'target-admin@example.com',
|
||||
]);
|
||||
$targetAdmin->assignRole('admin');
|
||||
|
||||
$targetEditor = User::factory()->create([
|
||||
'email' => 'target-editor@example.com',
|
||||
]);
|
||||
$targetEditor->assignRole('editor');
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->set('search', 'target-admin@example.com')
|
||||
->call('loginAsUser', $targetAdmin->id)
|
||||
->assertRedirect(route('me.dashboard'));
|
||||
|
||||
$this->assertAuthenticatedAs($targetAdmin);
|
||||
expect(session(UserImpersonation::SessionKey))->toBe($admin->id);
|
||||
$this->get(route('dashboard'))->assertRedirect(route('me.dashboard'));
|
||||
|
||||
$this->actingAs($admin);
|
||||
session()->forget(UserImpersonation::SessionKey);
|
||||
|
||||
LivewireVolt::test('admin.users')
|
||||
->set('search', 'target-editor@example.com')
|
||||
->call('loginAsUser', $targetEditor->id)
|
||||
->assertRedirect(route('me.dashboard'));
|
||||
|
||||
$this->assertAuthenticatedAs($targetEditor);
|
||||
expect(session(UserImpersonation::SessionKey))->toBe($admin->id);
|
||||
$this->get(route('dashboard'))->assertRedirect(route('me.dashboard'));
|
||||
});
|
||||
|
||||
test('impersonated user can return to the admin account', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('admin');
|
||||
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->actingAs($customer)
|
||||
->withSession([UserImpersonation::SessionKey => $admin->id])
|
||||
->post(route('admin.impersonate.leave'))
|
||||
->assertRedirect(route('admin.users.index'))
|
||||
->assertSessionHas('status', 'Erfolgreich zurück zum Admin-Account gewechselt.')
|
||||
->assertSessionMissing(UserImpersonation::SessionKey);
|
||||
|
||||
$this->assertAuthenticatedAs($admin);
|
||||
});
|
||||
|
||||
test('invalid impersonation session is cleared on leave', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->actingAs($customer)
|
||||
->withSession([UserImpersonation::SessionKey => 999999])
|
||||
->post(route('admin.impersonate.leave'))
|
||||
->assertRedirect(route('me.dashboard'))
|
||||
->assertSessionMissing(UserImpersonation::SessionKey);
|
||||
|
||||
$this->assertAuthenticatedAs($customer);
|
||||
});
|
||||
1255
tests/Feature/Admin/UserManagementTest.php
Normal file
1255
tests/Feature/Admin/UserManagementTest.php
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue