423 lines
13 KiB
PHP
423 lines
13 KiB
PHP
<?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 = 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.');
|
|
});
|