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); });