20-02-2026

This commit is contained in:
Kevin Adametz 2026-02-20 17:57:50 +01:00
parent 854ce02bf6
commit 4d6b4930b2
128 changed files with 18247 additions and 2093 deletions

View file

@ -0,0 +1,622 @@
<?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 createPartnerWithHub(): 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 createProductForPartner(Partner $partner, array $overrides = []): Product
{
$category = Category::factory()->create();
$product = Product::factory()->create(array_merge([
'partner_id' => $partner->id,
'product_type' => ProductType::SmartOrder,
'status' => ProductStatus::Pending,
'price_type' => PriceType::Fixed,
'name' => 'Test Produkt',
'description_short' => 'Kurzbeschreibung',
'description_long' => 'Langbeschreibung',
'b2in_article_number' => 'B2IN-000001',
], $overrides));
$product->categories()->attach($category->id);
$product->variants()->create([
'is_master_variant' => true,
'sku' => 'EDIT-SKU-001',
'selling_price' => 125000,
'purchase_price' => 68000,
'msrp' => 149900,
'availability_status' => 'in_stock',
'delivery_time_text' => '4-6 Wochen',
'currency' => 'EUR',
'variant_weight_g' => 45000,
'is_active' => true,
]);
return $product;
}
// --- Access Tests ---
test('owner can access product edit page', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user)
->get(route('products.edit.standard', $product))
->assertSuccessful();
});
test('admin can access any product edit page', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Admin');
$otherPartner = Partner::factory()->setupCompleted()->create();
$product = createProductForPartner($otherPartner);
$this->actingAs($user)
->get(route('products.edit.standard', $product))
->assertSuccessful();
});
test('other partner cannot access product edit page', function () {
[$user] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$otherPartner = Partner::factory()->setupCompleted()->create();
$product = createProductForPartner($otherPartner);
$this->actingAs($user)
->get(route('products.edit.standard', $product))
->assertForbidden();
});
test('customer cannot access product edit page', function () {
$customer = User::factory()->create();
$customer->assignRole('Customer');
$partner = Partner::factory()->setupCompleted()->create();
$product = createProductForPartner($partner);
$this->actingAs($customer)
->get(route('products.edit.standard', $product))
->assertForbidden();
});
// --- Mount Pre-Fill Tests ---
test('edit page pre-fills product data', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, [
'name' => 'Vorhandenes Sofa',
'description_short' => 'Ein tolles Sofa.',
'country_of_origin' => 'DE',
'main_material' => 'Buche',
'assembly_service' => true,
'service_radius_km' => 50,
'warranty_months' => 24,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('name', 'Vorhandenes Sofa')
->assertSet('descriptionShort', 'Ein tolles Sofa.')
->assertSet('countryOfOrigin', 'DE')
->assertSet('mainMaterial', 'Buche')
->assertSet('assemblyService', true)
->assertSet('serviceRadiusKm', 50)
->assertSet('warrantyMonths', 24);
});
test('edit page pre-fills variant data', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('sku', 'EDIT-SKU-001')
->assertSet('sellingPrice', 1250.00)
->assertSet('purchasePrice', 680.00)
->assertSet('msrp', 1499.00)
->assertSet('currency', 'EUR');
});
// --- Save / Update Tests ---
test('edit saves updated product fields', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('name', 'Aktualisiertes Sofa')
->set('descriptionShort', 'Neue Kurzbeschreibung.')
->set('mainMaterial', 'Eiche massiv')
->set('warrantyMonths', 36)
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->name)->toBe('Aktualisiertes Sofa');
expect($product->description_short)->toBe('Neue Kurzbeschreibung.');
expect($product->main_material)->toBe('Eiche massiv');
expect($product->warranty_months)->toBe(36);
});
test('edit saves updated variant data', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('sku', 'UPDATED-SKU')
->set('sellingPrice', 1500.00)
->set('currency', 'CHF')
->call('save')
->assertHasNoErrors();
$variant = $product->variants()->where('is_master_variant', true)->first();
$variant->refresh();
expect($variant->sku)->toBe('UPDATED-SKU');
expect($variant->selling_price)->toBe(150000);
expect($variant->currency)->toBe('CHF');
});
test('edit re-submits to pending when status is active', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, ['status' => ProductStatus::Draft]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('status', 'active')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Pending);
});
test('edit keeps draft status when saving as draft', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, ['status' => ProductStatus::Pending]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('status', 'draft')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Draft);
});
test('edit with correction status re-submits to pending', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, [
'status' => ProductStatus::Correction,
'curation_notes' => 'Bitte Bilder verbessern.',
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('status', 'active')
->call('save')
->assertHasNoErrors();
$product->refresh();
expect($product->status)->toBe(ProductStatus::Pending);
});
// --- Activity Logging Tests ---
test('edit creates activity log entry on save', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('name', 'Updated 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);
});
test('create creates activity log entry on save', function () {
Storage::fake('public');
[$user] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$category = Category::factory()->create();
$this->actingAs($user);
Volt::test('products.form-standard')
->set('name', 'Activity-Log Produkt')
->set('descriptionShort', 'Produkt mit Activity.')
->set('categoryId', $category->id)
->set('priceType', 'fixed')
->set('status', 'active')
->call('save')
->assertHasNoErrors();
$product = Product::where('name', 'Activity-Log Produkt')->first();
$activity = ProductActivity::where('product_id', $product->id)->first();
expect($activity)->not->toBeNull();
expect($activity->action)->toBe('created');
expect($activity->user_id)->toBe($user->id);
});
// --- Validation Tests ---
test('edit requires name', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('name', '')
->call('save')
->assertHasErrors(['name' => 'required']);
});
test('edit requires short description', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('descriptionShort', '')
->call('save')
->assertHasErrors(['descriptionShort' => 'required']);
});
test('edit allows own sku without unique error', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$this->actingAs($user);
// Keeping the same SKU should not trigger unique error
Volt::test('products.form-standard', ['product' => $product])
->assertSet('sku', 'EDIT-SKU-001')
->call('save')
->assertHasNoErrors();
});
test('edit rejects duplicate sku from other product', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
// Create first product with SKU
$product1 = createProductForPartner($partner);
// Create second product with different SKU
$product2 = Product::factory()->create([
'partner_id' => $partner->id,
'product_type' => ProductType::SmartOrder,
'status' => ProductStatus::Pending,
'price_type' => PriceType::Fixed,
'description_short' => 'Kurz',
'b2in_article_number' => 'B2IN-000002',
]);
$product2->categories()->attach(Category::factory()->create()->id);
$product2->variants()->create([
'is_master_variant' => true,
'sku' => 'OTHER-SKU',
'selling_price' => 50000,
'is_active' => true,
]);
$this->actingAs($user);
// Try to change product2's SKU to product1's SKU
Volt::test('products.form-standard', ['product' => $product2])
->set('sku', 'EDIT-SKU-001')
->call('save')
->assertHasErrors(['sku' => 'unique']);
});
// --- Existing Media Tests ---
test('edit shows existing media', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
// Add media
$product->media()->create([
'file_path' => 'products/1/test.jpg',
'type' => 'image',
'alt_text' => 'Test Image',
'order_column' => 1,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('existingMedia', fn ($value) => count($value) === 1 && $value[0]['alt_text'] === 'Test Image');
});
test('edit can remove existing media', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
// Create a fake file
Storage::disk('public')->put('products/1/test.jpg', 'fake-image-content');
$media = $product->media()->create([
'file_path' => 'products/1/test.jpg',
'type' => 'image',
'alt_text' => 'Test Image',
'order_column' => 1,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['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/test.jpg');
});
test('edit can reorder existing media via drag and drop', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$media1 = $product->media()->create([
'file_path' => 'products/1/first.jpg',
'type' => 'image',
'alt_text' => 'First Image',
'order_column' => 1,
]);
$media2 = $product->media()->create([
'file_path' => 'products/1/second.jpg',
'type' => 'image',
'alt_text' => 'Second Image',
'order_column' => 2,
]);
$media3 = $product->media()->create([
'file_path' => 'products/1/third.jpg',
'type' => 'image',
'alt_text' => 'Third Image',
'order_column' => 3,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('existingMedia', fn ($value) => count($value) === 3 && $value[0]['id'] === $media1->id)
->call('updateMediaOrder', [$media3->id, $media1->id, $media2->id])
->assertSet('existingMedia', fn ($value) => $value[0]['id'] === $media3->id
&& $value[1]['id'] === $media1->id
&& $value[2]['id'] === $media2->id
);
expect($media3->fresh()->order_column)->toBe(1);
expect($media1->fresh()->order_column)->toBe(2);
expect($media2->fresh()->order_column)->toBe(3);
});
test('edit existing media is loaded sorted by order column', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($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-standard', ['product' => $product])
->assertSet('existingMedia', fn ($value) => $value[0]['id'] === $media1->id && $value[1]['id'] === $media2->id);
});
test('edit media order includes order_column in existing media', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$product->media()->create([
'file_path' => 'products/1/test.jpg',
'type' => 'image',
'alt_text' => 'Test',
'order_column' => 5,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('existingMedia', fn ($value) => isset($value[0]['order_column']) && $value[0]['order_column'] === 5);
});
// --- Wood Origins Tests ---
test('edit pre-fills wood origins from product', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$product->woodOrigins()->create([
'wood_species' => 'Quercus robur',
'origin_country' => 'PL',
'origin_region' => 'Masowien',
'harvest_year' => 2024,
'sustainability_certificate' => 'FSC',
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->assertSet('woodOrigins', fn ($value) => count($value) === 1
&& $value[0]['wood_species'] === 'Quercus robur'
&& $value[0]['origin_country'] === 'PL'
);
});
test('edit saves updated wood origins', function () {
Storage::fake('public');
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$product->woodOrigins()->create([
'wood_species' => 'Old Species',
'origin_country' => 'DE',
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('woodOrigins', [
[
'wood_species' => 'New Species',
'origin_country' => 'AT',
'origin_region' => 'Tirol',
'harvest_year' => 2025,
'forest_operator' => 'Forstbetrieb',
'sustainability_certificate' => 'PEFC',
'eudr_reference_id' => 'EUDR-2025-AT',
],
])
->call('save')
->assertHasNoErrors();
$product->refresh();
$origins = $product->woodOrigins;
expect($origins)->toHaveCount(1);
expect($origins->first()->wood_species)->toBe('New Species');
expect($origins->first()->origin_country)->toBe('AT');
expect($origins->first()->origin_region)->toBe('Tirol');
});
// --- Activity Log Display ---
test('edit shows activity history in zuordnung tab', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner);
$product->activities()->create([
'user_id' => $user->id,
'action' => 'created',
'note' => null,
]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->set('activeTab', 'zuordnung')
->assertSeeText('created');
});
// --- Archive / Sold from Edit Form ---
test('edit can archive a product', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, ['status' => ProductStatus::Active]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->call('archiveProduct')
->assertRedirect(route('products.index'));
$product->refresh();
expect($product->status)->toBe(ProductStatus::Archived);
});
test('edit can mark a product as sold', function () {
[$user, $partner] = createPartnerWithHub();
$user->assignRole('Manufacturer');
$product = createProductForPartner($partner, ['status' => ProductStatus::Active]);
$this->actingAs($user);
Volt::test('products.form-standard', ['product' => $product])
->call('markAsSold')
->assertRedirect(route('products.index'));
$product->refresh();
expect($product->status)->toBe(ProductStatus::Sold);
});