20-02-2026
This commit is contained in:
parent
854ce02bf6
commit
4d6b4930b2
128 changed files with 18247 additions and 2093 deletions
|
|
@ -4,7 +4,9 @@ use App\Models\User;
|
|||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
|
||||
test('login screen can be rendered', function () {
|
||||
$response = $this->get('/login');
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$response = $this->get($portalUrl.'/login');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
|
|
@ -38,9 +40,11 @@ test('users can not authenticate with invalid password', function () {
|
|||
});
|
||||
|
||||
test('users can logout', function () {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->post('/logout');
|
||||
$response = $this->actingAs($user)->post($portalUrl.'/logout');
|
||||
|
||||
$response->assertRedirect('/');
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ use Illuminate\Support\Facades\Event;
|
|||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
test('email verification screen can be rendered', function () {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$response = $this->actingAs($user)->get('/verify-email');
|
||||
$response = $this->actingAs($user)->get($portalUrl.'/verify-email');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
|
|
@ -29,7 +31,7 @@ test('email can be verified', function () {
|
|||
Event::assertDispatched(Verified::class);
|
||||
|
||||
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
|
||||
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||
$response->assertRedirect(route('partner.setup.wizard', absolute: false));
|
||||
});
|
||||
|
||||
test('email is not verified with invalid hash', function () {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ use App\Models\User;
|
|||
use Livewire\Volt\Volt;
|
||||
|
||||
test('confirm password screen can be rendered', function () {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->get('/confirm-password');
|
||||
$response = $this->actingAs($user)->get($portalUrl.'/confirm-password');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
use App\Notifications\CustomResetPasswordNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('reset password link screen can be rendered', function () {
|
||||
$response = $this->get('/forgot-password');
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$response = $this->get($portalUrl.'/forgot-password');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
|
|
@ -20,7 +22,7 @@ test('reset password link can be requested', function () {
|
|||
->set('email', $user->email)
|
||||
->call('sendPasswordResetLink');
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class);
|
||||
Notification::assertSentTo($user, CustomResetPasswordNotification::class);
|
||||
});
|
||||
|
||||
test('reset password screen can be rendered', function () {
|
||||
|
|
@ -32,8 +34,10 @@ test('reset password screen can be rendered', function () {
|
|||
->set('email', $user->email)
|
||||
->call('sendPasswordResetLink');
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
|
||||
$response = $this->get('/reset-password/'.$notification->token);
|
||||
Notification::assertSentTo($user, CustomResetPasswordNotification::class, function ($notification) {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$response = $this->get($portalUrl.'/reset-password/'.$notification->token);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
|
@ -50,7 +54,7 @@ test('password can be reset with valid token', function () {
|
|||
->set('email', $user->email)
|
||||
->call('sendPasswordResetLink');
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
|
||||
Notification::assertSentTo($user, CustomResetPasswordNotification::class, function ($notification) use ($user) {
|
||||
$response = Volt::test('auth.reset-password', ['token' => $notification->token])
|
||||
->set('email', $user->email)
|
||||
->set('password', 'password')
|
||||
|
|
@ -59,7 +63,7 @@ test('password can be reset with valid token', function () {
|
|||
|
||||
$response
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('login', absolute: false));
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
return true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,54 @@
|
|||
<?php
|
||||
|
||||
use App\Models\RegistrationCode;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Volt;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
test('registration screen can be rendered', function () {
|
||||
$response = $this->get('/register');
|
||||
test('registration landing page can be rendered for valid role', function () {
|
||||
Role::create([
|
||||
'name' => 'Broker',
|
||||
'guard_name' => 'web',
|
||||
'reg_prefix' => 'M',
|
||||
'can_be_invited' => true,
|
||||
'reg_start_number' => 10000001,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response = $this->get('/reg/m');
|
||||
|
||||
$response->assertOk();
|
||||
});
|
||||
|
||||
test('new users can register', function () {
|
||||
test('registration landing page returns 404 for invalid role', function () {
|
||||
$response = $this->get('/reg/invalid');
|
||||
|
||||
$response->assertNotFound();
|
||||
});
|
||||
|
||||
test('registration requires valid code in session', function () {
|
||||
$response = Volt::test('auth.register')
|
||||
->set('name', 'Test User')
|
||||
->set('email', 'test@example.com')
|
||||
->set('password', 'password')
|
||||
->set('password_confirmation', 'password')
|
||||
->call('register');
|
||||
|
||||
$response->assertHasErrors('registration_code');
|
||||
});
|
||||
|
||||
test('new users can register with valid registration code', function () {
|
||||
$code = RegistrationCode::create([
|
||||
'code' => 'M10000001',
|
||||
'role' => 'broker',
|
||||
'name' => 'Test Makler',
|
||||
'status' => RegistrationCode::STATUS_AVAILABLE,
|
||||
]);
|
||||
|
||||
session([
|
||||
'registration_code_id' => $code->id,
|
||||
'registration_role' => 'broker',
|
||||
]);
|
||||
|
||||
$response = Volt::test('auth.register')
|
||||
->set('name', 'Test User')
|
||||
->set('email', 'test@example.com')
|
||||
|
|
@ -21,4 +61,7 @@ test('new users can register', function () {
|
|||
->assertRedirect(route('dashboard', absolute: false));
|
||||
|
||||
$this->assertAuthenticated();
|
||||
|
||||
expect(User::where('email', 'test@example.com')->exists())->toBeTrue();
|
||||
expect($code->fresh()->status)->toBe(RegistrationCode::STATUS_USED);
|
||||
});
|
||||
|
|
|
|||
95
tests/Feature/CreateNewUserOriginTest.php
Normal file
95
tests/Feature/CreateNewUserOriginTest.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Enums\UserOrigin;
|
||||
use App\Models\Hub;
|
||||
|
||||
test('new user gets style2own origin when theme is style2own', function () {
|
||||
config(['app.theme' => 'style2own']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
]);
|
||||
|
||||
expect($user->origin)->toBe(UserOrigin::Style2Own);
|
||||
expect($user->origin->value)->toBe('style2own');
|
||||
});
|
||||
|
||||
test('new user gets stileigentum origin when theme is stileigentum', function () {
|
||||
config(['app.theme' => 'stileigentum']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'Test User 2',
|
||||
'email' => 'test2@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
]);
|
||||
|
||||
expect($user->origin)->toBe(UserOrigin::StilEigentum);
|
||||
expect($user->origin->value)->toBe('stileigentum');
|
||||
});
|
||||
|
||||
test('new user has null origin when theme is unknown', function () {
|
||||
config(['app.theme' => 'portal']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'Test User 3',
|
||||
'email' => 'test3@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
]);
|
||||
|
||||
expect($user->origin)->toBeNull();
|
||||
});
|
||||
|
||||
test('new user has null origin when theme is empty', function () {
|
||||
config(['app.theme' => '']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'Test User 4',
|
||||
'email' => 'test4@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
]);
|
||||
|
||||
expect($user->origin)->toBeNull();
|
||||
});
|
||||
|
||||
test('new user stores hub_id when provided', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
config(['app.theme' => '']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'Hub User',
|
||||
'email' => 'hubuser@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
'hub_id' => $hub->id,
|
||||
]);
|
||||
|
||||
expect($user->hub_id)->toBe($hub->id);
|
||||
});
|
||||
|
||||
test('new user has null hub_id when not provided', function () {
|
||||
config(['app.theme' => '']);
|
||||
|
||||
$action = new CreateNewUser;
|
||||
$user = $action->create([
|
||||
'name' => 'No Hub User',
|
||||
'email' => 'nohub@example.com',
|
||||
'password' => 'Password123!',
|
||||
'password_confirmation' => 'Password123!',
|
||||
]);
|
||||
|
||||
expect($user->hub_id)->toBeNull();
|
||||
});
|
||||
|
|
@ -3,14 +3,21 @@
|
|||
use App\Models\User;
|
||||
|
||||
test('guests are redirected to the login page', function () {
|
||||
$response = $this->get('/dashboard');
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$response = $this->get($portalUrl.'/dashboard');
|
||||
|
||||
$response->assertRedirect('/login');
|
||||
});
|
||||
|
||||
test('authenticated users can visit the dashboard', function () {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get('/dashboard');
|
||||
$response = $this->get($portalUrl.'/dashboard');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
|
|
|
|||
280
tests/Feature/LocalFeedTest.php
Normal file
280
tests/Feature/LocalFeedTest.php
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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\User;
|
||||
use Livewire\Volt\Volt;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
Role::create(['name' => 'Admin']);
|
||||
Role::create(['name' => 'Retailer']);
|
||||
Role::create(['name' => 'Customer']);
|
||||
});
|
||||
|
||||
test('customer only sees curated active available products from own hub', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$otherHub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$customer = User::factory()->create(['hub_id' => $hub->id]);
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
// Sichtbares Produkt: aktiv + kuratiert + verfügbar + gleicher Hub
|
||||
$visibleProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
// Nicht sichtbar: nicht kuratiert
|
||||
$notCurated = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => false,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
// Nicht sichtbar: anderen Hub
|
||||
$otherHubProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $otherHub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
// Nicht sichtbar: Draft-Status
|
||||
$draftProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Draft,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertSee($visibleProduct->name)
|
||||
->assertDontSee($notCurated->name)
|
||||
->assertDontSee($otherHubProduct->name)
|
||||
->assertDontSee($draftProduct->name);
|
||||
});
|
||||
|
||||
test('admin sees all products regardless of curation or status', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
|
||||
$activeProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$draftProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Draft,
|
||||
'is_curated' => false,
|
||||
'is_available' => false,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertSee($activeProduct->name)
|
||||
->assertSee($draftProduct->name);
|
||||
});
|
||||
|
||||
test('retailer only sees own products', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$otherPartner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$retailer = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
$ownProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
]);
|
||||
|
||||
$otherProduct = Product::factory()->create([
|
||||
'partner_id' => $otherPartner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
]);
|
||||
|
||||
$this->actingAs($retailer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertSee($ownProduct->name)
|
||||
->assertDontSee($otherProduct->name);
|
||||
});
|
||||
|
||||
test('customer can search products by name', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$customer = User::factory()->create(['hub_id' => $hub->id]);
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
$matchingProduct = Product::factory()->create([
|
||||
'name' => 'Eiche Sideboard',
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$otherProduct = Product::factory()->create([
|
||||
'name' => 'Massivholztisch',
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->set('search', 'Eiche')
|
||||
->assertSee($matchingProduct->name)
|
||||
->assertDontSee($otherProduct->name);
|
||||
});
|
||||
|
||||
test('customer can filter products by category', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$customer = User::factory()->create(['hub_id' => $hub->id]);
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
$categoryProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
$categoryProduct->categories()->attach($category);
|
||||
|
||||
$noCategory = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->set('categoryFilter', (string) $category->id)
|
||||
->assertSee($categoryProduct->name)
|
||||
->assertDontSee($noCategory->name);
|
||||
});
|
||||
|
||||
test('retailer can filter products by product type', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$retailer = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
$teaserProduct = Product::factory()->create([
|
||||
'name' => 'Teaser Sideboard',
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'product_type' => ProductType::LocalStock,
|
||||
'status' => ProductStatus::Active,
|
||||
]);
|
||||
|
||||
$standardProduct = Product::factory()->create([
|
||||
'name' => 'Standard Sofa',
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'product_type' => ProductType::SmartOrder,
|
||||
'status' => ProductStatus::Active,
|
||||
]);
|
||||
|
||||
$this->actingAs($retailer);
|
||||
|
||||
// Filter auf Teaser (LocalStock)
|
||||
Volt::test('products.index')
|
||||
->set('productTypeFilter', 'local_stock')
|
||||
->assertSee($teaserProduct->name)
|
||||
->assertDontSee($standardProduct->name);
|
||||
|
||||
// Filter auf Standard (SmartOrder)
|
||||
Volt::test('products.index')
|
||||
->set('productTypeFilter', 'smart_order')
|
||||
->assertSee($standardProduct->name)
|
||||
->assertDontSee($teaserProduct->name);
|
||||
|
||||
// Kein Filter – beide sichtbar
|
||||
Volt::test('products.index')
|
||||
->set('productTypeFilter', '')
|
||||
->assertSee($teaserProduct->name)
|
||||
->assertSee($standardProduct->name);
|
||||
});
|
||||
|
||||
test('product list shows both create buttons for retailer', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$retailer = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
$this->actingAs($retailer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertSee(__('Neues Teaser-Produkt'))
|
||||
->assertSee(__('Neues Standard-Produkt'));
|
||||
});
|
||||
|
||||
test('product list shows both create buttons for manufacturer', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$manufacturer = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$manufacturer->assignRole('Retailer'); // Need Manufacturer role
|
||||
Role::create(['name' => 'Manufacturer']);
|
||||
$manufacturer->syncRoles(['Manufacturer']);
|
||||
|
||||
$this->actingAs($manufacturer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertSee(__('Neues Teaser-Produkt'))
|
||||
->assertSee(__('Neues Standard-Produkt'));
|
||||
});
|
||||
|
||||
test('product list does not show create buttons for customer', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$customer = User::factory()->create(['hub_id' => $hub->id]);
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
Volt::test('products.index')
|
||||
->assertDontSee(__('Neues Teaser-Produkt'))
|
||||
->assertDontSee(__('Neues Standard-Produkt'));
|
||||
});
|
||||
81
tests/Feature/Models/PartnerRelationshipsTest.php
Normal file
81
tests/Feature/Models/PartnerRelationshipsTest.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Hub;
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
|
||||
test('partner belongs to hub', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->create(['hub_id' => $hub->id]);
|
||||
|
||||
expect($partner->hub->id)->toBe($hub->id);
|
||||
});
|
||||
|
||||
test('partner has many users', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
User::factory()->count(3)->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect($partner->users)->toHaveCount(3);
|
||||
});
|
||||
|
||||
test('partner has many products', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
Product::factory()->count(2)->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect($partner->products)->toHaveCount(2);
|
||||
});
|
||||
|
||||
test('partner can have parent partner (broker)', function () {
|
||||
$broker = Partner::factory()->estateAgent()->create();
|
||||
$customer = Partner::factory()->create(['parent_partner_id' => $broker->id]);
|
||||
|
||||
expect($customer->parentPartner->id)->toBe($broker->id);
|
||||
expect($customer->broker()->first()->id)->toBe($broker->id);
|
||||
});
|
||||
|
||||
test('partner can have child partners (customers)', function () {
|
||||
$broker = Partner::factory()->estateAgent()->create();
|
||||
Partner::factory()->count(2)->create(['parent_partner_id' => $broker->id]);
|
||||
|
||||
expect($broker->childPartners)->toHaveCount(2);
|
||||
expect($broker->customers)->toHaveCount(2);
|
||||
});
|
||||
|
||||
test('partner factory has retailer state', function () {
|
||||
$partner = Partner::factory()->retailer()->create();
|
||||
|
||||
expect($partner->type->value)->toBe('Retailer');
|
||||
});
|
||||
|
||||
test('partner factory has manufacturer state', function () {
|
||||
$partner = Partner::factory()->manufacturer()->create();
|
||||
|
||||
expect($partner->type->value)->toBe('Manufacturer');
|
||||
});
|
||||
|
||||
test('partner factory has estateAgent state', function () {
|
||||
$partner = Partner::factory()->estateAgent()->create();
|
||||
|
||||
expect($partner->type->value)->toBe('Estate-Agent');
|
||||
});
|
||||
|
||||
test('partner casts opening_hours to array', function () {
|
||||
$hours = ['mon' => '09:00-18:00', 'tue' => '09:00-18:00'];
|
||||
$partner = Partner::factory()->create(['opening_hours' => $hours]);
|
||||
|
||||
$partner->refresh();
|
||||
|
||||
expect($partner->opening_hours)->toBeArray();
|
||||
expect($partner->opening_hours['mon'])->toBe('09:00-18:00');
|
||||
});
|
||||
|
||||
test('partner casts specialties to array', function () {
|
||||
$specialties = ['Küchen', 'Wohnzimmer', 'Schlafzimmer'];
|
||||
$partner = Partner::factory()->create(['specialties' => $specialties]);
|
||||
|
||||
$partner->refresh();
|
||||
|
||||
expect($partner->specialties)->toBeArray();
|
||||
expect($partner->specialties)->toHaveCount(3);
|
||||
});
|
||||
172
tests/Feature/Models/ProductTest.php
Normal file
172
tests/Feature/Models/ProductTest.php
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PriceType;
|
||||
use App\Enums\ProductStatus;
|
||||
use App\Enums\ProductType;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Category;
|
||||
use App\Models\Collection;
|
||||
use App\Models\Hub;
|
||||
use App\Models\Media;
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\Tag;
|
||||
|
||||
test('product can be created with factory', function () {
|
||||
$product = Product::factory()->create();
|
||||
|
||||
expect($product)->toBeInstanceOf(Product::class);
|
||||
expect($product->id)->toBeInt();
|
||||
expect($product->name)->toBeString();
|
||||
expect($product->slug)->toBeString();
|
||||
});
|
||||
|
||||
test('product casts product_type to enum', function () {
|
||||
$product = Product::factory()->create(['product_type' => 'local_stock']);
|
||||
|
||||
expect($product->product_type)->toBe(ProductType::LocalStock);
|
||||
});
|
||||
|
||||
test('product casts status to enum', function () {
|
||||
$product = Product::factory()->create(['status' => 'draft']);
|
||||
|
||||
expect($product->status)->toBe(ProductStatus::Draft);
|
||||
});
|
||||
|
||||
test('product casts price_type to enum', function () {
|
||||
$product = Product::factory()->create(['price_type' => 'from_price']);
|
||||
|
||||
expect($product->price_type)->toBe(PriceType::FromPrice);
|
||||
});
|
||||
|
||||
test('product belongs to partner', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect($product->partner->id)->toBe($partner->id);
|
||||
});
|
||||
|
||||
test('product belongs to hub', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$product = Product::factory()->create(['hub_id' => $hub->id]);
|
||||
|
||||
expect($product->hub->id)->toBe($hub->id);
|
||||
});
|
||||
|
||||
test('product belongs to brand', function () {
|
||||
$partner = Partner::factory()->manufacturer()->create();
|
||||
$brand = Brand::factory()->create(['partner_id' => $partner->id]);
|
||||
$product = Product::factory()->create(['brand_id' => $brand->id]);
|
||||
|
||||
expect($product->brand->id)->toBe($brand->id);
|
||||
});
|
||||
|
||||
test('product belongs to collection', function () {
|
||||
$collection = Collection::create(['name' => 'Test Kollektion', 'slug' => 'test-kollektion']);
|
||||
$product = Product::factory()->create(['collection_id' => $collection->id]);
|
||||
|
||||
expect($product->collection->id)->toBe($collection->id);
|
||||
});
|
||||
|
||||
test('product can have categories', function () {
|
||||
$product = Product::factory()->create();
|
||||
$category = Category::create(['name' => 'Sofas', 'slug' => 'sofas']);
|
||||
|
||||
$product->categories()->attach($category);
|
||||
|
||||
expect($product->categories)->toHaveCount(1);
|
||||
expect($product->categories->first()->name)->toBe('Sofas');
|
||||
});
|
||||
|
||||
test('product can have tags', function () {
|
||||
$product = Product::factory()->create();
|
||||
$tag = Tag::create(['name' => 'Neu', 'slug' => 'neu']);
|
||||
|
||||
$product->tags()->attach($tag);
|
||||
|
||||
expect($product->tags)->toHaveCount(1);
|
||||
expect($product->tags->first()->name)->toBe('Neu');
|
||||
});
|
||||
|
||||
test('product can have media', function () {
|
||||
$product = Product::factory()->create();
|
||||
$media = Media::factory()->create([
|
||||
'model_type' => Product::class,
|
||||
'model_id' => $product->id,
|
||||
]);
|
||||
|
||||
expect($product->media)->toHaveCount(1);
|
||||
expect($product->media->first()->file_path)->toBe($media->file_path);
|
||||
});
|
||||
|
||||
test('product scope active filters correctly', function () {
|
||||
Product::factory()->create(['status' => ProductStatus::Active]);
|
||||
Product::factory()->create(['status' => ProductStatus::Draft]);
|
||||
Product::factory()->create(['status' => ProductStatus::Archived]);
|
||||
|
||||
expect(Product::active()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('product scope curated filters correctly', function () {
|
||||
Product::factory()->create(['is_curated' => true]);
|
||||
Product::factory()->create(['is_curated' => false]);
|
||||
|
||||
expect(Product::curated()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('product scope localStock filters correctly', function () {
|
||||
Product::factory()->localStock()->create();
|
||||
Product::factory()->smartOrder()->create();
|
||||
|
||||
expect(Product::localStock()->count())->toBe(1);
|
||||
expect(Product::smartOrder()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('product scope inHub filters correctly', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$otherHub = Hub::factory()->create();
|
||||
|
||||
Product::factory()->create(['hub_id' => $hub->id]);
|
||||
Product::factory()->create(['hub_id' => $otherHub->id]);
|
||||
|
||||
expect(Product::inHub($hub->id)->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('product scope available combines active curated and available', function () {
|
||||
Product::factory()->create([
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
Product::factory()->create([
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => false,
|
||||
'is_available' => true,
|
||||
]);
|
||||
Product::factory()->create([
|
||||
'status' => ProductStatus::Draft,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
expect(Product::available()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('product factory has localStock state', function () {
|
||||
$product = Product::factory()->localStock()->create();
|
||||
|
||||
expect($product->product_type)->toBe(ProductType::LocalStock);
|
||||
});
|
||||
|
||||
test('product factory has smartOrder state', function () {
|
||||
$product = Product::factory()->smartOrder()->create();
|
||||
|
||||
expect($product->product_type)->toBe(ProductType::SmartOrder);
|
||||
});
|
||||
|
||||
test('product factory has active state', function () {
|
||||
$product = Product::factory()->active()->create();
|
||||
|
||||
expect($product->status)->toBe(ProductStatus::Active);
|
||||
expect($product->is_curated)->toBeTrue();
|
||||
});
|
||||
48
tests/Feature/Models/ProductWoodOriginTest.php
Normal file
48
tests/Feature/Models/ProductWoodOriginTest.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductWoodOrigin;
|
||||
|
||||
test('product wood origin can be created with factory', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
$origin = ProductWoodOrigin::factory()->create(['product_id' => $product->id]);
|
||||
|
||||
expect($origin)->toBeInstanceOf(ProductWoodOrigin::class);
|
||||
expect($origin->wood_species)->not->toBeEmpty();
|
||||
expect($origin->origin_country)->toHaveLength(2);
|
||||
});
|
||||
|
||||
test('product wood origin belongs to product', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
$origin = ProductWoodOrigin::factory()->create(['product_id' => $product->id]);
|
||||
|
||||
expect($origin->product)->toBeInstanceOf(Product::class);
|
||||
expect($origin->product->id)->toBe($product->id);
|
||||
});
|
||||
|
||||
test('product has many wood origins', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
ProductWoodOrigin::factory()->count(3)->create(['product_id' => $product->id]);
|
||||
|
||||
expect($product->woodOrigins)->toHaveCount(3);
|
||||
});
|
||||
|
||||
test('deleting product cascades to wood origins', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
ProductWoodOrigin::factory()->count(2)->create(['product_id' => $product->id]);
|
||||
|
||||
expect(ProductWoodOrigin::where('product_id', $product->id)->count())->toBe(2);
|
||||
|
||||
$product->delete();
|
||||
|
||||
expect(ProductWoodOrigin::where('product_id', $product->id)->count())->toBe(0);
|
||||
});
|
||||
53
tests/Feature/Models/SettingTest.php
Normal file
53
tests/Feature/Models/SettingTest.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Setting;
|
||||
|
||||
test('setting can store and retrieve a string value', function () {
|
||||
Setting::create([
|
||||
'group' => 'test',
|
||||
'key' => 'string_val',
|
||||
'value' => 'hello',
|
||||
'type' => 'string',
|
||||
]);
|
||||
|
||||
expect(Setting::getValue('test', 'string_val'))->toBe('hello');
|
||||
});
|
||||
|
||||
test('setting can store and retrieve an integer value', function () {
|
||||
Setting::create([
|
||||
'group' => 'test',
|
||||
'key' => 'int_val',
|
||||
'value' => '42',
|
||||
'type' => 'integer',
|
||||
]);
|
||||
|
||||
expect(Setting::getValue('test', 'int_val'))->toBe(42);
|
||||
});
|
||||
|
||||
test('setting can store and retrieve a boolean value', function () {
|
||||
Setting::create([
|
||||
'group' => 'test',
|
||||
'key' => 'bool_val',
|
||||
'value' => 'true',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
|
||||
expect(Setting::getValue('test', 'bool_val'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('setting returns default when key does not exist', function () {
|
||||
expect(Setting::getValue('nonexistent', 'key', 'default'))->toBe('default');
|
||||
});
|
||||
|
||||
test('setting can update value', function () {
|
||||
Setting::create([
|
||||
'group' => 'test',
|
||||
'key' => 'update_val',
|
||||
'value' => 'old',
|
||||
'type' => 'string',
|
||||
]);
|
||||
|
||||
Setting::setValue('test', 'update_val', 'new');
|
||||
|
||||
expect(Setting::getValue('test', 'update_val'))->toBe('new');
|
||||
});
|
||||
39
tests/Feature/Models/UserRelationshipsTest.php
Normal file
39
tests/Feature/Models/UserRelationshipsTest.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\UserOrigin;
|
||||
use App\Models\Hub;
|
||||
use App\Models\Partner;
|
||||
use App\Models\User;
|
||||
|
||||
test('user can have a hub', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$user = User::factory()->create(['hub_id' => $hub->id]);
|
||||
|
||||
expect($user->hub->id)->toBe($hub->id);
|
||||
});
|
||||
|
||||
test('user can have an origin', function () {
|
||||
$user = User::factory()->create(['origin' => 'style2own']);
|
||||
|
||||
expect($user->origin)->toBe(UserOrigin::Style2Own);
|
||||
});
|
||||
|
||||
test('user origin casts stileigentum correctly', function () {
|
||||
$user = User::factory()->create(['origin' => 'stileigentum']);
|
||||
|
||||
expect($user->origin)->toBe(UserOrigin::StilEigentum);
|
||||
expect($user->origin->tonality())->toBe('sie');
|
||||
});
|
||||
|
||||
test('user origin can be null', function () {
|
||||
$user = User::factory()->create(['origin' => null]);
|
||||
|
||||
expect($user->origin)->toBeNull();
|
||||
});
|
||||
|
||||
test('user belongs to partner', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect($user->partner->id)->toBe($partner->id);
|
||||
});
|
||||
96
tests/Feature/PartnerPolicyTest.php
Normal file
96
tests/Feature/PartnerPolicyTest.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Partner;
|
||||
use App\Models\User;
|
||||
use App\Policies\PartnerPolicy;
|
||||
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');
|
||||
});
|
||||
|
||||
test('admin can view any partner', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
|
||||
expect((new PartnerPolicy)->viewAny($admin))->toBeTrue();
|
||||
});
|
||||
|
||||
test('non-admin cannot view any partner', function () {
|
||||
$retailer = User::factory()->create();
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->viewAny($retailer))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can view any specific partner', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create();
|
||||
|
||||
expect((new PartnerPolicy)->view($admin, $partner))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user can view own partner', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->view($user, $partner))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user cannot view other partner', function () {
|
||||
$otherPartner = Partner::factory()->create();
|
||||
$myPartner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $myPartner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->view($user, $otherPartner))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can update any partner', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create();
|
||||
|
||||
expect((new PartnerPolicy)->update($admin, $partner))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user can update own partner', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->update($user, $partner))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user cannot update other partner', function () {
|
||||
$otherPartner = Partner::factory()->create();
|
||||
$myPartner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $myPartner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->update($user, $otherPartner))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can curate products', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
|
||||
expect((new PartnerPolicy)->curateProducts($admin))->toBeTrue();
|
||||
});
|
||||
|
||||
test('retailer cannot curate products', function () {
|
||||
$retailer = User::factory()->create();
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
expect((new PartnerPolicy)->curateProducts($retailer))->toBeFalse();
|
||||
});
|
||||
107
tests/Feature/PartnerProfilePageTest.php
Normal file
107
tests/Feature/PartnerProfilePageTest.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\ProductStatus;
|
||||
use App\Models\Hub;
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Livewire\Volt\Volt;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
Role::create(['name' => 'Retailer']);
|
||||
Role::create(['name' => 'Customer']);
|
||||
});
|
||||
|
||||
test('authenticated user can view partner profile page', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create([
|
||||
'hub_id' => $hub->id,
|
||||
'company_name' => 'Tischlerei Müller GmbH',
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Volt::test('partner.profile', ['partnerId' => $partner->id])
|
||||
->assertSee($partner->company_name);
|
||||
});
|
||||
|
||||
test('partner profile shows display name when set', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create([
|
||||
'hub_id' => $hub->id,
|
||||
'company_name' => 'Tischlerei Müller GmbH',
|
||||
'display_name' => 'Tischlerei Müller',
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Volt::test('partner.profile', ['partnerId' => $partner->id])
|
||||
->assertSee('Tischlerei Müller')
|
||||
->assertSee('Tischlerei Müller GmbH');
|
||||
});
|
||||
|
||||
test('partner profile shows story text when present', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create([
|
||||
'hub_id' => $hub->id,
|
||||
'story_text' => 'Wir sind seit 1985 eine regionale Tischlerei.',
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Volt::test('partner.profile', ['partnerId' => $partner->id])
|
||||
->assertSee('Wir sind seit 1985 eine regionale Tischlerei.');
|
||||
});
|
||||
|
||||
test('partner profile only shows active curated available products', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$visibleProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Active,
|
||||
'is_curated' => true,
|
||||
'is_available' => true,
|
||||
]);
|
||||
|
||||
$draftProduct = Product::factory()->create([
|
||||
'partner_id' => $partner->id,
|
||||
'hub_id' => $hub->id,
|
||||
'status' => ProductStatus::Draft,
|
||||
'is_curated' => false,
|
||||
'is_available' => false,
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Volt::test('partner.profile', ['partnerId' => $partner->id])
|
||||
->assertSee($visibleProduct->name)
|
||||
->assertDontSee($draftProduct->name);
|
||||
});
|
||||
|
||||
test('partner profile throws model not found for non-existent partner', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
expect(fn () => Volt::test('partner.profile', ['partnerId' => 99999]))
|
||||
->toThrow(ModelNotFoundException::class);
|
||||
});
|
||||
|
||||
test('unauthenticated user is redirected from partner profile', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
|
||||
$this->get(route('partner.profile', ['partnerId' => $partner->id]))
|
||||
->assertRedirect(route('login'));
|
||||
});
|
||||
135
tests/Feature/PartnerProfileUpdateTest.php
Normal file
135
tests/Feature/PartnerProfileUpdateTest.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Hub;
|
||||
use App\Models\Partner;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Volt;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
Role::create(['name' => 'Admin']);
|
||||
Role::create(['name' => 'Retailer']);
|
||||
});
|
||||
|
||||
test('admin can access partner edit page', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->setupCompleted()->create();
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.partners.edit', $partner->id))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('partner can access own profile edit page', function () {
|
||||
$partner = Partner::factory()->setupCompleted()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('admin.partners.edit', $partner->id))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('partner cannot access other partner edit page', function () {
|
||||
$myPartner = Partner::factory()->setupCompleted()->create();
|
||||
$otherPartner = Partner::factory()->setupCompleted()->create();
|
||||
$user = User::factory()->create(['partner_id' => $myPartner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('admin.partners.edit', $otherPartner->id))
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update partner profile', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create(['company_name' => 'Alter Name']);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.partners.edit', ['partnerId' => $partner->id])
|
||||
->set('companyName', 'Neuer Firmenname')
|
||||
->set('city', 'Herford')
|
||||
->set('storyText', 'Seit 1985 sind wir für Sie da.')
|
||||
->set('foundedYear', 1985)
|
||||
->set('specialtiesInput', 'Sofas, Küchen, Betten')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$partner->refresh();
|
||||
expect($partner->company_name)->toBe('Neuer Firmenname');
|
||||
expect($partner->city)->toBe('Herford');
|
||||
expect($partner->story_text)->toBe('Seit 1985 sind wir für Sie da.');
|
||||
expect($partner->founded_year)->toBe(1985);
|
||||
expect($partner->specialties)->toContain('Sofas');
|
||||
});
|
||||
|
||||
test('partner profile update validates required company name', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.partners.edit', ['partnerId' => $partner->id])
|
||||
->set('companyName', '')
|
||||
->call('save')
|
||||
->assertHasErrors(['companyName' => 'required']);
|
||||
});
|
||||
|
||||
test('partner profile update validates url format', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.partners.edit', ['partnerId' => $partner->id])
|
||||
->set('companyName', 'Test GmbH')
|
||||
->set('website', 'kein-url')
|
||||
->call('save')
|
||||
->assertHasErrors(['website' => 'url']);
|
||||
});
|
||||
|
||||
test('partner profile saves opening hours', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$partner = Partner::factory()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.partners.edit', ['partnerId' => $partner->id])
|
||||
->set('companyName', $partner->company_name)
|
||||
->set('openingHours.monday.open', '08:00')
|
||||
->set('openingHours.monday.close', '20:00')
|
||||
->set('openingHours.sunday.closed', true)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$partner->refresh();
|
||||
expect($partner->opening_hours['monday']['open'])->toBe('08:00');
|
||||
expect($partner->opening_hours['sunday']['closed'])->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner can update own hub assignment', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Volt::test('admin.partners.edit', ['partnerId' => $partner->id])
|
||||
->set('companyName', $partner->company_name)
|
||||
->set('hubId', $hub->id)
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$partner->refresh();
|
||||
expect($partner->hub_id)->toBe($hub->id);
|
||||
});
|
||||
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.');
|
||||
});
|
||||
622
tests/Feature/ProductEditTest.php
Normal file
622
tests/Feature/ProductEditTest.php
Normal 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);
|
||||
});
|
||||
131
tests/Feature/ProductPolicyTest.php
Normal file
131
tests/Feature/ProductPolicyTest.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Partner;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
use App\Policies\ProductPolicy;
|
||||
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']);
|
||||
Role::create(['name' => 'Manufacturer']);
|
||||
Role::create(['name' => 'Customer']);
|
||||
Permission::create(['name' => 'curate products']);
|
||||
Role::findByName('Admin')->givePermissionTo('curate products');
|
||||
});
|
||||
|
||||
test('admin can view any product', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
|
||||
expect((new ProductPolicy)->viewAny($admin))->toBeTrue();
|
||||
});
|
||||
|
||||
test('retailer can view any product', function () {
|
||||
$retailer = User::factory()->create();
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
expect((new ProductPolicy)->viewAny($retailer))->toBeTrue();
|
||||
});
|
||||
|
||||
test('customer cannot view any product in backend', function () {
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
expect((new ProductPolicy)->viewAny($customer))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can view specific product', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$product = Product::factory()->create();
|
||||
|
||||
expect((new ProductPolicy)->view($admin, $product))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user can view own product', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect((new ProductPolicy)->view($user, $product))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner user cannot view other partner product', function () {
|
||||
$myPartner = Partner::factory()->create();
|
||||
$otherPartner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $myPartner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
$product = Product::factory()->create(['partner_id' => $otherPartner->id]);
|
||||
|
||||
expect((new ProductPolicy)->view($user, $product))->toBeFalse();
|
||||
});
|
||||
|
||||
test('retailer can create products', function () {
|
||||
$retailer = User::factory()->create();
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
expect((new ProductPolicy)->create($retailer))->toBeTrue();
|
||||
});
|
||||
|
||||
test('manufacturer can create products', function () {
|
||||
$manufacturer = User::factory()->create();
|
||||
$manufacturer->assignRole('Manufacturer');
|
||||
|
||||
expect((new ProductPolicy)->create($manufacturer))->toBeTrue();
|
||||
});
|
||||
|
||||
test('customer cannot create products', function () {
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
expect((new ProductPolicy)->create($customer))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can update any product', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
$product = Product::factory()->create();
|
||||
|
||||
expect((new ProductPolicy)->update($admin, $product))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner can update own product', function () {
|
||||
$partner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
$product = Product::factory()->create(['partner_id' => $partner->id]);
|
||||
|
||||
expect((new ProductPolicy)->update($user, $product))->toBeTrue();
|
||||
});
|
||||
|
||||
test('partner cannot update other partner product', function () {
|
||||
$myPartner = Partner::factory()->create();
|
||||
$otherPartner = Partner::factory()->create();
|
||||
$user = User::factory()->create(['partner_id' => $myPartner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
$product = Product::factory()->create(['partner_id' => $otherPartner->id]);
|
||||
|
||||
expect((new ProductPolicy)->update($user, $product))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can curate products', function () {
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Admin');
|
||||
|
||||
expect((new ProductPolicy)->curate($admin))->toBeTrue();
|
||||
});
|
||||
|
||||
test('retailer cannot curate products', function () {
|
||||
$retailer = User::factory()->create();
|
||||
$retailer->assignRole('Retailer');
|
||||
|
||||
expect((new ProductPolicy)->curate($retailer))->toBeFalse();
|
||||
});
|
||||
75
tests/Feature/RestoreBackupSeederTest.php
Normal file
75
tests/Feature/RestoreBackupSeederTest.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\Partner;
|
||||
use App\Models\RegistrationCode;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RestoreBackupSeeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->seed(RestoreBackupSeeder::class);
|
||||
});
|
||||
|
||||
test('seeder creates expected number of roles', function () {
|
||||
expect(Role::count())->toBe(6);
|
||||
});
|
||||
|
||||
test('seeder creates expected roles with correct names', function () {
|
||||
expect(Role::pluck('name')->sort()->values()->all())
|
||||
->toBe(['Admin', 'Broker', 'Customer', 'Manufacturer', 'Retailer', 'Super-Admin']);
|
||||
});
|
||||
|
||||
test('seeder creates all 21 permissions', function () {
|
||||
expect(Permission::count())->toBe(21);
|
||||
});
|
||||
|
||||
test('seeder creates 12 partners', function () {
|
||||
expect(Partner::count())->toBe(12);
|
||||
});
|
||||
|
||||
test('seeder creates 13 users including soft-deleted', function () {
|
||||
expect(User::withTrashed()->count())->toBe(13);
|
||||
expect(User::onlyTrashed()->count())->toBe(4);
|
||||
});
|
||||
|
||||
test('seeder assigns correct roles to users', function () {
|
||||
$admin = User::find(1);
|
||||
expect($admin)->not->toBeNull()
|
||||
->and($admin->hasRole('Admin'))->toBeTrue();
|
||||
|
||||
$manufacturer = User::find(11);
|
||||
expect($manufacturer)->not->toBeNull()
|
||||
->and($manufacturer->hasRole('Manufacturer'))->toBeTrue();
|
||||
|
||||
$retailer = User::find(15);
|
||||
expect($retailer)->not->toBeNull()
|
||||
->and($retailer->hasRole('Retailer'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('seeder creates 2 brands linked to partners', function () {
|
||||
expect(Brand::count())->toBe(2);
|
||||
expect(Brand::find(1)->partner_id)->toBe(15);
|
||||
expect(Brand::find(2)->partner_id)->toBe(10);
|
||||
});
|
||||
|
||||
test('seeder creates 20 registration codes', function () {
|
||||
expect(RegistrationCode::count())->toBe(20);
|
||||
});
|
||||
|
||||
test('seeder preserves user-partner relationships', function () {
|
||||
$user = User::find(12);
|
||||
expect($user->partner_id)->toBe(11);
|
||||
expect($user->partner->company_name)->toBe('Max Möbelmann');
|
||||
});
|
||||
|
||||
test('seeder can be run multiple times without errors', function () {
|
||||
$this->seed(RestoreBackupSeeder::class);
|
||||
|
||||
expect(User::withTrashed()->count())->toBe(13);
|
||||
expect(Role::count())->toBe(6);
|
||||
expect(Partner::count())->toBe(12);
|
||||
});
|
||||
|
|
@ -3,10 +3,10 @@
|
|||
use App\Models\User;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('profile page is displayed', function () {
|
||||
$this->actingAs($user = User::factory()->create());
|
||||
test('profile page requires authentication', function () {
|
||||
$portalUrl = 'https://'.config('domains.domain_portal');
|
||||
|
||||
$this->get('/settings/profile')->assertOk();
|
||||
$this->get($portalUrl.'/settings/profile')->assertRedirect();
|
||||
});
|
||||
|
||||
test('profile information can be updated', function () {
|
||||
|
|
@ -56,7 +56,7 @@ test('user can delete their account', function () {
|
|||
->assertHasNoErrors()
|
||||
->assertRedirect('/');
|
||||
|
||||
expect($user->fresh())->toBeNull();
|
||||
expect($user->fresh()->trashed())->toBeTrue();
|
||||
expect(auth()->check())->toBeFalse();
|
||||
});
|
||||
|
||||
|
|
|
|||
1027
tests/Feature/StandardProductCreateTest.php
Normal file
1027
tests/Feature/StandardProductCreateTest.php
Normal file
File diff suppressed because it is too large
Load diff
330
tests/Feature/TeaserProductCreateTest.php
Normal file
330
tests/Feature/TeaserProductCreateTest.php
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
<?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\User;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
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']);
|
||||
});
|
||||
|
||||
function makeRetailerWithHub(): array
|
||||
{
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Retailer');
|
||||
|
||||
return [$user, $partner, $hub];
|
||||
}
|
||||
|
||||
test('retailer can access teaser product creation page', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('products.create.teaser'))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('manufacturer can access teaser product creation page', function () {
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Manufacturer');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('products.create.teaser'))
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('customer cannot access teaser product creation page', function () {
|
||||
$customer = User::factory()->create();
|
||||
$customer->assignRole('Customer');
|
||||
|
||||
$this->actingAs($customer)
|
||||
->get(route('products.create.teaser'))
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('retailer can create teaser product with from_price', function () {
|
||||
Storage::fake('public');
|
||||
[$user, $partner, $hub] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Sideboard Eiche')
|
||||
->set('descriptionShort', 'Schönes Massivholz-Sideboard aus regionaler Fertigung.')
|
||||
->set('priceType', 'from_price')
|
||||
->set('priceDisplayText', 'Ab 1.800 €')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->set('mainImages', [UploadedFile::fake()->image('product.jpg', 800, 600)])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Sideboard Eiche')->first();
|
||||
|
||||
expect($product)->not->toBeNull();
|
||||
expect($product->product_type)->toBe(ProductType::LocalStock);
|
||||
expect($product->price_type)->toBe(PriceType::FromPrice);
|
||||
expect($product->price_display_text)->toBe('Ab 1.800 €');
|
||||
expect($product->partner_id)->toBe($partner->id);
|
||||
expect($product->hub_id)->toBe($hub->id);
|
||||
expect($product->is_curated)->toBeFalse();
|
||||
});
|
||||
|
||||
test('retailer can create teaser product with on_request price', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Küche Massivholz')
|
||||
->set('descriptionShort', 'Individuelle Küche auf Maß.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'draft')
|
||||
->set('mainImages', [UploadedFile::fake()->image('kitchen.jpg', 800, 600)])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Küche Massivholz')->first();
|
||||
expect($product->price_type)->toBe(PriceType::OnRequest);
|
||||
});
|
||||
|
||||
test('teaser product rejects fixed price type', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Produkt mit Festpreis')
|
||||
->set('descriptionShort', 'Beschreibung.')
|
||||
->set('priceType', 'fixed')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->call('save')
|
||||
->assertHasErrors(['priceType']);
|
||||
});
|
||||
|
||||
test('teaser product requires price display text when from_price', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Produkt')
|
||||
->set('descriptionShort', 'Beschreibung.')
|
||||
->set('priceType', 'from_price')
|
||||
->set('priceDisplayText', '')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->call('save')
|
||||
->assertHasErrors(['priceDisplayText']);
|
||||
});
|
||||
|
||||
test('teaser product requires name', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', '')
|
||||
->set('descriptionShort', 'Beschreibung.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->call('save')
|
||||
->assertHasErrors(['name' => 'required']);
|
||||
});
|
||||
|
||||
test('teaser product description short max 180 characters', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Test')
|
||||
->set('descriptionShort', str_repeat('x', 181))
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->call('save')
|
||||
->assertHasErrors(['descriptionShort' => 'max']);
|
||||
});
|
||||
|
||||
test('teaser product requires category', function () {
|
||||
[$user] = makeRetailerWithHub();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Test Produkt')
|
||||
->set('descriptionShort', 'Beschreibung.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', null)
|
||||
->call('save')
|
||||
->assertHasErrors(['categoryId' => 'required']);
|
||||
});
|
||||
|
||||
test('manufacturer can create teaser product', function () {
|
||||
Storage::fake('public');
|
||||
$hub = Hub::factory()->create();
|
||||
$partner = Partner::factory()->setupCompleted()->create(['hub_id' => $hub->id]);
|
||||
$user = User::factory()->create(['partner_id' => $partner->id]);
|
||||
$user->assignRole('Manufacturer');
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Hersteller Teaser-Produkt')
|
||||
->set('descriptionShort', 'Ein Teaser-Produkt vom Hersteller.')
|
||||
->set('priceType', 'from_price')
|
||||
->set('priceDisplayText', 'Ab 3.200 €')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->set('mainImages', [UploadedFile::fake()->image('product.jpg', 800, 600)])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Hersteller Teaser-Produkt')->first();
|
||||
|
||||
expect($product)->not->toBeNull();
|
||||
expect($product->product_type)->toBe(ProductType::LocalStock);
|
||||
expect($product->partner_id)->toBe($partner->id);
|
||||
expect($product->hub_id)->toBe($hub->id);
|
||||
});
|
||||
|
||||
test('created teaser product is automatically not curated', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Nicht kuratiertes Produkt')
|
||||
->set('descriptionShort', 'Test.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->set('mainImages', [UploadedFile::fake()->image('test.jpg', 800, 600)])
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Nicht kuratiertes Produkt')->first();
|
||||
expect($product->is_curated)->toBeFalse();
|
||||
expect($product->product_type)->toBe(ProductType::LocalStock);
|
||||
});
|
||||
|
||||
test('submitting teaser product as active sets status to pending', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Pending Teaser')
|
||||
->set('descriptionShort', 'Test zur Freigabe.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Pending Teaser')->first();
|
||||
expect($product->status)->toBe(ProductStatus::Pending);
|
||||
});
|
||||
|
||||
test('saving teaser product as draft keeps status as draft', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Draft Teaser')
|
||||
->set('descriptionShort', 'Test Entwurf.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'draft')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Draft Teaser')->first();
|
||||
expect($product->status)->toBe(ProductStatus::Draft);
|
||||
});
|
||||
|
||||
test('teaser product generates b2in article number on create', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Teaser mit Nummer')
|
||||
->set('descriptionShort', 'Test Artikelnummer.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('status', 'active')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Teaser mit Nummer')->first();
|
||||
expect($product->b2in_article_number)->toStartWith('B2IN-');
|
||||
expect($product->b2in_article_number)->toBe('B2IN-000001');
|
||||
});
|
||||
|
||||
test('teaser product saves partner product number', function () {
|
||||
Storage::fake('public');
|
||||
[$user] = makeRetailerWithHub();
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->set('name', 'Teaser mit Partnernummer')
|
||||
->set('descriptionShort', 'Test Partnernummer.')
|
||||
->set('priceType', 'on_request')
|
||||
->set('categoryId', $category->id)
|
||||
->set('partnerProductNumber', 'MY-CUSTOM-001')
|
||||
->set('status', 'active')
|
||||
->call('save')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$product = Product::where('name', 'Teaser mit Partnernummer')->first();
|
||||
expect($product->partner_product_number)->toBe('MY-CUSTOM-001');
|
||||
});
|
||||
|
||||
test('teaser product auto-generates partner product number on mount', function () {
|
||||
[$user, $partner] = makeRetailerWithHub();
|
||||
$this->actingAs($user);
|
||||
|
||||
$expectedNumber = sprintf('P%03d-%04d', $partner->id, 1);
|
||||
|
||||
Livewire\Volt\Volt::test('products.form-teaser')
|
||||
->assertSet('partnerProductNumber', $expectedNumber);
|
||||
});
|
||||
519
tests/Feature/TeaserProductEditTest.php
Normal file
519
tests/Feature/TeaserProductEditTest.php
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
<?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);
|
||||
});
|
||||
24
tests/Unit/Enums/ProductStatusTest.php
Normal file
24
tests/Unit/Enums/ProductStatusTest.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\ProductStatus;
|
||||
|
||||
test('product status has correct values', function () {
|
||||
expect(ProductStatus::Draft->value)->toBe('draft');
|
||||
expect(ProductStatus::Pending->value)->toBe('pending');
|
||||
expect(ProductStatus::Correction->value)->toBe('correction');
|
||||
expect(ProductStatus::Active->value)->toBe('active');
|
||||
expect(ProductStatus::Archived->value)->toBe('archived');
|
||||
expect(ProductStatus::Sold->value)->toBe('sold');
|
||||
});
|
||||
|
||||
test('product status has labels', function () {
|
||||
foreach (ProductStatus::cases() as $status) {
|
||||
expect($status->label())->toBeString()->not->toBeEmpty();
|
||||
}
|
||||
});
|
||||
|
||||
test('product status has colors', function () {
|
||||
foreach (ProductStatus::cases() as $status) {
|
||||
expect($status->color())->toBeString()->not->toBeEmpty();
|
||||
}
|
||||
});
|
||||
47
tests/Unit/Enums/ProductTypeTest.php
Normal file
47
tests/Unit/Enums/ProductTypeTest.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PriceType;
|
||||
use App\Enums\ProductType;
|
||||
|
||||
test('product type has correct values', function () {
|
||||
expect(ProductType::LocalStock->value)->toBe('local_stock');
|
||||
expect(ProductType::SmartOrder->value)->toBe('smart_order');
|
||||
});
|
||||
|
||||
test('product type has labels', function () {
|
||||
expect(ProductType::LocalStock->label())->toBeString()->not->toBeEmpty();
|
||||
expect(ProductType::SmartOrder->label())->toBeString()->not->toBeEmpty();
|
||||
});
|
||||
|
||||
test('product type can be created from value', function () {
|
||||
expect(ProductType::from('local_stock'))->toBe(ProductType::LocalStock);
|
||||
expect(ProductType::from('smart_order'))->toBe(ProductType::SmartOrder);
|
||||
});
|
||||
|
||||
// Typ A – Ticket zwingend (Teaser-Produkte: Beratungspflicht, Abschluss nur im Laden)
|
||||
test('local stock requires ticket', function () {
|
||||
expect(ProductType::LocalStock->requiresTicket())->toBeTrue();
|
||||
});
|
||||
|
||||
// Typ B – Ticket optional (Standard-Produkte: Direktkauf online möglich)
|
||||
test('smart order does not require ticket', function () {
|
||||
expect(ProductType::SmartOrder->requiresTicket())->toBeFalse();
|
||||
});
|
||||
|
||||
// Typ A: Kein Festpreis online – nur Ab-Preis oder Preis auf Anfrage
|
||||
test('local stock only allows non-fixed price types', function () {
|
||||
$allowed = ProductType::LocalStock->allowedPriceTypes();
|
||||
|
||||
expect($allowed)->toContain(PriceType::FromPrice)
|
||||
->toContain(PriceType::OnRequest)
|
||||
->not->toContain(PriceType::Fixed);
|
||||
});
|
||||
|
||||
// Typ B: Alle Preistypen erlaubt
|
||||
test('smart order allows all price types', function () {
|
||||
$allowed = ProductType::SmartOrder->allowedPriceTypes();
|
||||
|
||||
expect($allowed)->toContain(PriceType::Fixed)
|
||||
->toContain(PriceType::FromPrice)
|
||||
->toContain(PriceType::OnRequest);
|
||||
});
|
||||
13
tests/Unit/Enums/UserOriginTest.php
Normal file
13
tests/Unit/Enums/UserOriginTest.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\UserOrigin;
|
||||
|
||||
test('user origin has correct values', function () {
|
||||
expect(UserOrigin::Style2Own->value)->toBe('style2own');
|
||||
expect(UserOrigin::StilEigentum->value)->toBe('stileigentum');
|
||||
});
|
||||
|
||||
test('user origin has tonality', function () {
|
||||
expect(UserOrigin::Style2Own->tonality())->toBe('du');
|
||||
expect(UserOrigin::StilEigentum->tonality())->toBe('sie');
|
||||
});
|
||||
9
tests/bootstrap.php
Normal file
9
tests/bootstrap.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Config-Cache entfernen, damit phpunit.xml-Umgebungsvariablen (SQLite) greifen
|
||||
$configCache = __DIR__.'/../bootstrap/cache/config.php';
|
||||
if (file_exists($configCache)) {
|
||||
unlink($configCache);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue