20-02-2026
This commit is contained in:
parent
854ce02bf6
commit
4d6b4930b2
128 changed files with 18247 additions and 2093 deletions
423
tests/Feature/ProductCurationTest.php
Normal file
423
tests/Feature/ProductCurationTest.php
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\ProductStatus;
|
||||
use App\Enums\ProductType;
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductActivity;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Volt;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
Role::create(['name' => 'Admin']);
|
||||
Role::create(['name' => 'Retailer']);
|
||||
Permission::create(['name' => 'curate products']);
|
||||
Role::findByName('Admin')->givePermissionTo('curate products');
|
||||
});
|
||||
|
||||
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.');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue