b2in/tests/Feature/TeaserProductEditTest.php
2026-02-20 17:57:50 +01:00

519 lines
16 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\PriceType;
use App\Enums\ProductStatus;
use App\Enums\ProductType;
use App\Models\Category;
use App\Models\Hub;
use App\Models\Partner;
use App\Models\Product;
use App\Models\ProductActivity;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
use Livewire\Volt\Volt;
use Spatie\Permission\Models\Role;
beforeEach(function () {
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
Role::create(['name' => 'Retailer']);
Role::create(['name' => 'Manufacturer']);
Role::create(['name' => 'Customer']);
Role::create(['name' => 'Admin']);
});
function createTeaserPartnerWithHub(): array
{
$hub = Hub::factory()->create();
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
$user = User::factory()->create(['partner_id' => $partner->id]);
return [$user, $partner, $hub];
}
function createTeaserProduct(Partner $partner, array $overrides = []): Product
{
$category = Category::factory()->create();
$product = Product::factory()->create(array_merge([
'partner_id' => $partner->id,
'product_type' => ProductType::LocalStock,
'status' => ProductStatus::Pending,
'price_type' => PriceType::FromPrice,
'price_display_text' => 'Ab 2.500 €',
'name' => 'Teaser Produkt',
'description_short' => 'Kurzbeschreibung Teaser',
'is_available' => true,
'is_curated' => false,
], $overrides));
$product->categories()->attach($category->id);
return $product;
}
// --- Access Tests ---
test('owner can access teaser product edit page', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user)
->get(route('products.edit.teaser', $product))
->assertSuccessful();
});
test('admin can access any teaser product edit page', function () {
[$user] = createTeaserPartnerWithHub();
$user->assignRole('Admin');
$otherPartner = Partner::factory()->setupCompleted()->create();
$product = createTeaserProduct($otherPartner);
$this->actingAs($user)
->get(route('products.edit.teaser', $product))
->assertSuccessful();
});
test('other partner cannot access teaser product edit page', function () {
[$user] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$otherPartner = Partner::factory()->setupCompleted()->create();
$product = createTeaserProduct($otherPartner);
$this->actingAs($user)
->get(route('products.edit.teaser', $product))
->assertForbidden();
});
test('customer cannot access teaser product edit page', function () {
$customer = User::factory()->create();
$customer->assignRole('Customer');
$partner = Partner::factory()->setupCompleted()->create();
$product = createTeaserProduct($partner);
$this->actingAs($customer)
->get(route('products.edit.teaser', $product))
->assertForbidden();
});
// --- Mount Pre-Fill Tests ---
test('teaser edit page pre-fills product data', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, [
'name' => 'Vorhandenes Sideboard',
'description_short' => 'Ein tolles Sideboard.',
'price_type' => PriceType::FromPrice,
'price_display_text' => 'Ab 3.000 €',
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('name', 'Vorhandenes Sideboard')
->assertSet('descriptionShort', 'Ein tolles Sideboard.')
->assertSet('priceType', 'from_price')
->assertSet('priceDisplayText', 'Ab 3.000 €')
->assertSet('partnerProductNumber', '');
});
test('teaser edit page pre-fills category', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
$expectedCategoryId = $product->categories->first()->id;
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('categoryId', $expectedCategoryId);
});
test('teaser edit page pre-fills partner product number', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, [
'partner_product_number' => 'MY-NUM-001',
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('partnerProductNumber', 'MY-NUM-001');
});
test('teaser edit saves updated partner product number', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('partnerProductNumber', 'UPDATED-123')
->call('save')
->assertHasNoErrors();
expect($product->fresh()->partner_product_number)->toBe('UPDATED-123');
});
test('teaser edit maps pending status to active for UI', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, ['status' => ProductStatus::Pending]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('status', 'active');
});
test('teaser edit maps correction status to active for UI', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, [
'status' => ProductStatus::Correction,
'curation_notes' => 'Bitte Bild verbessern.',
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('status', 'active');
});
test('teaser edit maps draft status correctly', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, ['status' => ProductStatus::Draft]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('status', 'draft');
});
// --- Save / Update Tests ---
test('teaser edit saves updated product fields', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('name', 'Aktualisiertes Sideboard')
->set('descriptionShort', 'Neue Kurzbeschreibung.')
->set('priceDisplayText', 'Ab 4.000 €')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->name)->toBe('Aktualisiertes Sideboard');
expect($product->description_short)->toBe('Neue Kurzbeschreibung.');
expect($product->price_display_text)->toBe('Ab 4.000 €');
});
test('teaser edit re-submits to pending when status is active', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, ['status' => ProductStatus::Draft]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('status', 'active')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Pending);
});
test('teaser edit keeps draft status when saving as draft', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, ['status' => ProductStatus::Pending]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('status', 'draft')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Draft);
});
test('teaser edit with correction status re-submits to pending', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, [
'status' => ProductStatus::Correction,
'curation_notes' => 'Bitte Bilder verbessern.',
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('status', 'active')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Pending);
});
test('teaser edit updates category', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$newCategory = Category::factory()->create();
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('categoryId', $newCategory->id)
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->categories->first()->id)->toBe($newCategory->id);
});
test('teaser edit changes price type to on_request', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner, [
'price_type' => PriceType::FromPrice,
'price_display_text' => 'Ab 2.500 €',
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('priceType', 'on_request')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->price_type)->toBe(PriceType::OnRequest);
});
// --- Activity Logging ---
test('teaser edit creates activity log entry on save', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('name', 'Updated Teaser Name')
->call('save')
->assertHasNoErrors();
$activity = ProductActivity::where('product_id', $product->id)->first();
expect($activity)->not->toBeNull();
expect($activity->action)->toBe('updated');
expect($activity->user_id)->toBe($user->id);
});
// --- Validation Tests ---
test('teaser edit requires name', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('name', '')
->call('save')
->assertHasErrors(['name' => 'required']);
});
test('teaser edit requires short description', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('descriptionShort', '')
->call('save')
->assertHasErrors(['descriptionShort' => 'required']);
});
test('teaser edit enforces max 180 chars for short description', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('descriptionShort', str_repeat('A', 181))
->call('save')
->assertHasErrors(['descriptionShort' => 'max']);
});
test('teaser edit requires category', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('categoryId', null)
->call('save')
->assertHasErrors(['categoryId' => 'required']);
});
test('teaser edit rejects fixed price type', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('priceType', 'fixed')
->call('save')
->assertHasErrors(['priceType']);
});
test('teaser edit requires price display text for from_price', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->set('priceType', 'from_price')
->set('priceDisplayText', '')
->call('save')
->assertHasErrors(['priceDisplayText']);
});
// --- Media Tests ---
test('teaser edit shows existing media', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$product->media()->create([
'file_path' => 'products/1/test.jpg',
'type' => 'image',
'alt_text' => 'Teaser Image',
'order_column' => 1,
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('existingMedia', fn ($value) => count($value) === 1 && $value[0]['alt_text'] === 'Teaser Image');
});
test('teaser edit can remove existing media', function () {
Storage::fake('public');
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
Storage::disk('public')->put('products/1/teaser.jpg', 'fake-image-content');
$media = $product->media()->create([
'file_path' => 'products/1/teaser.jpg',
'type' => 'image',
'alt_text' => 'Teaser Image',
'order_column' => 1,
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('existingMedia', fn ($value) => count($value) === 1)
->call('removeExistingMedia', $media->id)
->assertSet('existingMedia', fn ($value) => count($value) === 0);
expect($product->media()->count())->toBe(0);
Storage::disk('public')->assertMissing('products/1/teaser.jpg');
});
test('teaser edit can reorder existing media via drag and drop', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$media1 = $product->media()->create([
'file_path' => 'products/1/first.jpg',
'type' => 'image',
'alt_text' => 'First',
'order_column' => 1,
]);
$media2 = $product->media()->create([
'file_path' => 'products/1/second.jpg',
'type' => 'image',
'alt_text' => 'Second',
'order_column' => 2,
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('existingMedia', fn ($value) => count($value) === 2 && $value[0]['id'] === $media1->id)
->call('updateMediaOrder', [$media2->id, $media1->id])
->assertSet('existingMedia', fn ($value) => $value[0]['id'] === $media2->id && $value[1]['id'] === $media1->id);
expect($media2->fresh()->order_column)->toBe(1);
expect($media1->fresh()->order_column)->toBe(2);
});
test('teaser edit existing media is loaded sorted by order column', function () {
[$user, $partner] = createTeaserPartnerWithHub();
$user->assignRole('Retailer');
$product = createTeaserProduct($partner);
$media2 = $product->media()->create([
'file_path' => 'products/1/second.jpg',
'type' => 'image',
'alt_text' => 'Second',
'order_column' => 2,
]);
$media1 = $product->media()->create([
'file_path' => 'products/1/first.jpg',
'type' => 'image',
'alt_text' => 'First',
'order_column' => 1,
]);
$this->actingAs($user);
Volt::test('products.form-teaser', ['product' => $product])
->assertSet('existingMedia', fn ($value) => $value[0]['id'] === $media1->id && $value[1]['id'] === $media2->id);
});