forgetCachedPermissions(); Role::create(['name' => 'Admin']); Role::create(['name' => 'Retailer']); $permission = Permission::firstOrCreate(['name' => 'curate products']); Role::findByName('Admin')->givePermissionTo($permission); }); function makeAdminUser(): User { $admin = User::factory()->create(); $admin->assignRole('Admin'); return $admin; } // --- Access Tests --- test('admin can access admin products page', function () { $admin = makeAdminUser(); $this->actingAs($admin) ->get(route('admin.products.index')) ->assertSuccessful(); }); test('retailer cannot access admin products page', function () { $retailer = User::factory()->create(); $retailer->assignRole('Retailer'); $this->actingAs($retailer) ->get(route('admin.products.index')) ->assertForbidden(); }); // --- Approval Tests --- test('admin can approve a pending product', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('approve', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Active); expect($product->is_curated)->toBeTrue(); expect($product->curated_by)->toBe($admin->id); expect($product->curated_at)->not->toBeNull(); expect($product->curation_notes)->toBeNull(); }); test('approve creates activity log entry', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('approve', $product->id) ->assertHasNoErrors(); $activity = ProductActivity::where('product_id', $product->id)->first(); expect($activity)->not->toBeNull(); expect($activity->action)->toBe('approved'); expect($activity->user_id)->toBe($admin->id); }); // --- Correction Tests --- test('admin can send correction for a pending product', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openCorrection', $product->id) ->assertSet('correctingProductId', $product->id) ->set('curationNotes', 'Bitte Produktbilder in höherer Auflösung hochladen.') ->call('sendCorrection', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Correction); expect($product->is_curated)->toBeFalse(); expect($product->curation_notes)->toBe('Bitte Produktbilder in höherer Auflösung hochladen.'); }); test('correction requires notes', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openCorrection', $product->id) ->set('curationNotes', '') ->call('sendCorrection', $product->id) ->assertHasErrors(['curationNotes' => 'required']); }); test('correction creates activity log entry with notes', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openCorrection', $product->id) ->set('curationNotes', 'Bilder bitte verbessern.') ->call('sendCorrection', $product->id) ->assertHasNoErrors(); $activity = ProductActivity::where('product_id', $product->id)->first(); expect($activity)->not->toBeNull(); expect($activity->action)->toBe('correction'); expect($activity->note)->toBe('Bilder bitte verbessern.'); expect($activity->user_id)->toBe($admin->id); }); // --- Rejection Tests --- test('admin can reject a product with reason', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openRejection', $product->id) ->assertSet('rejectingProductId', $product->id) ->set('rejectionReason', 'Produkt entspricht nicht den Qualitätsstandards.') ->call('reject', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Archived); expect($product->is_curated)->toBeFalse(); expect($product->curation_notes)->toBe('Produkt entspricht nicht den Qualitätsstandards.'); }); test('rejection requires a reason', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openRejection', $product->id) ->set('rejectionReason', '') ->call('reject', $product->id) ->assertHasErrors(['rejectionReason' => 'required']); }); test('reject creates activity log entry with reason', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Pending, 'is_curated' => false, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('openRejection', $product->id) ->set('rejectionReason', 'Nicht geeignet für Plattform.') ->call('reject', $product->id) ->assertHasNoErrors(); $activity = ProductActivity::where('product_id', $product->id)->first(); expect($activity)->not->toBeNull(); expect($activity->action)->toBe('rejected'); expect($activity->note)->toBe('Nicht geeignet für Plattform.'); expect($activity->user_id)->toBe($admin->id); }); // --- Archive / Sold Tests --- test('admin can archive a product from list', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Active, 'is_curated' => true, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('archiveProduct', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Archived); }); test('admin can mark product as sold from list', function () { $admin = makeAdminUser(); $product = Product::factory()->create([ 'status' => ProductStatus::Active, 'is_curated' => true, ]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('markAsSold', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Sold); }); test('archive creates activity log entry', function () { $admin = makeAdminUser(); $product = Product::factory()->create(['status' => ProductStatus::Active]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('archiveProduct', $product->id); $activity = ProductActivity::where('product_id', $product->id)->first(); expect($activity)->not->toBeNull(); expect($activity->action)->toBe('archived'); }); test('mark as sold creates activity log entry', function () { $admin = makeAdminUser(); $product = Product::factory()->create(['status' => ProductStatus::Active]); $this->actingAs($admin); Volt::test('admin.products.index') ->call('markAsSold', $product->id); $activity = ProductActivity::where('product_id', $product->id)->first(); expect($activity)->not->toBeNull(); expect($activity->action)->toBe('sold'); }); // --- Filter Tests --- test('admin product list shows all products', function () { $admin = makeAdminUser(); $product = Product::factory()->create(['name' => 'Test Lampe Deluxe']); $this->actingAs($admin); Volt::test('admin.products.index') ->assertSeeText('Test Lampe Deluxe'); }); test('admin product list can filter by status', function () { $admin = makeAdminUser(); Product::factory()->create(['status' => ProductStatus::Pending, 'name' => 'Pending Product']); Product::factory()->create(['status' => ProductStatus::Active, 'name' => 'Active Product']); $this->actingAs($admin); Volt::test('admin.products.index') ->set('statusFilter', 'pending') ->assertSeeText('Pending Product') ->assertDontSeeText('Active Product'); }); test('admin product list can search by name', function () { $admin = makeAdminUser(); Product::factory()->create(['name' => 'Designer Stuhl']); Product::factory()->create(['name' => 'Vintage Lampe']); $this->actingAs($admin); Volt::test('admin.products.index') ->set('search', 'Stuhl') ->assertSeeText('Designer Stuhl') ->assertDontSeeText('Vintage Lampe'); }); test('admin product list can filter by partner', function () { $admin = makeAdminUser(); $partner1 = Partner::factory()->create(['company_name' => 'Möbelhaus A']); $partner2 = Partner::factory()->create(['company_name' => 'Möbelhaus B']); Product::factory()->create(['partner_id' => $partner1->id, 'name' => 'Stuhl A']); Product::factory()->create(['partner_id' => $partner2->id, 'name' => 'Stuhl B']); $this->actingAs($admin); Volt::test('admin.products.index') ->set('partnerFilter', $partner1->id) ->assertSeeText('Stuhl A') ->assertDontSeeText('Stuhl B'); }); test('retailer cannot approve products', function () { $retailer = User::factory()->create(); $retailer->assignRole('Retailer'); $this->actingAs($retailer); Volt::test('admin.products.index') ->assertForbidden(); }); // --- Product list archive/sold from retailer index --- test('retailer can archive own product from product list', function () { Role::findOrCreate('Retailer'); $partner = Partner::factory()->create(); $user = User::factory()->create(['partner_id' => $partner->id]); $user->assignRole('Retailer'); $product = Product::factory()->create([ 'partner_id' => $partner->id, 'status' => ProductStatus::Active, ]); $this->actingAs($user); Volt::test('products.index') ->call('archiveProduct', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Archived); }); test('retailer can mark own product as sold from product list', function () { Role::findOrCreate('Retailer'); $partner = Partner::factory()->create(); $user = User::factory()->create(['partner_id' => $partner->id]); $user->assignRole('Retailer'); $product = Product::factory()->create([ 'partner_id' => $partner->id, 'status' => ProductStatus::Active, ]); $this->actingAs($user); Volt::test('products.index') ->call('markAsSold', $product->id) ->assertHasNoErrors(); $product->refresh(); expect($product->status)->toBe(ProductStatus::Sold); }); // --- Curation notes display in edit form --- test('correction notes are displayed in standard product edit', function () { Role::findOrCreate('Retailer'); $partner = Partner::factory()->create(); $user = User::factory()->create(['partner_id' => $partner->id]); $user->assignRole('Retailer'); $product = Product::factory()->create([ 'partner_id' => $partner->id, 'status' => ProductStatus::Correction, 'product_type' => ProductType::SmartOrder, 'curation_notes' => 'Bitte bessere Bilder hochladen.', ]); $this->actingAs($user); Volt::test('products.form-standard', ['product' => $product]) ->assertSeeText('Korrektur erforderlich') ->assertSeeText('Bitte bessere Bilder hochladen.'); }); test('rejection notes are displayed in teaser product edit', function () { Role::findOrCreate('Retailer'); $partner = Partner::factory()->create(); $user = User::factory()->create(['partner_id' => $partner->id]); $user->assignRole('Retailer'); $product = Product::factory()->create([ 'partner_id' => $partner->id, 'status' => ProductStatus::Archived, 'product_type' => ProductType::LocalStock, 'curation_notes' => 'Produkt nicht für die Plattform geeignet.', ]); $this->actingAs($user); Volt::test('products.form-teaser', ['product' => $product]) ->assertSeeText('Produkt abgelehnt') ->assertSeeText('Produkt nicht für die Plattform geeignet.'); });