parfum/tests/Feature/Livewire/Studio/Catalog/Perfume/EditTest.php

481 lines
17 KiB
PHP

<?php
use App\Enums\Concentration;
use App\Models\Brand;
use App\Models\Category;
use App\Models\Employee;
use App\Models\Outlet;
use App\Models\Perfume;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->ownerRole = Role::where('name', 'Owner')->first();
$this->user->roles()->attach($this->ownerRole->id);
// Create test data
$this->outlets = Outlet::factory()->count(3)->create();
$this->brands = Brand::factory()->count(2)->create();
$this->categories = Category::factory()->count(3)->create();
});
function mountPerfumeEditComponent(User $user, Perfume $perfume)
{
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Perfume\Edit::class, ['perfume' => $perfume->hash]);
}
/*
|--------------------------------------------------------------------------
| Edit Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders perfume edit page successfully', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->assertViewIs('livewire.studio.catalog.perfume.form')
->assertViewHas('pageTitle', 'Ubah Parfum');
});
it('populates perfume form with perfume data on mount', function () {
$brand = $this->brands->first();
$perfume = Perfume::factory()->create([
'name' => 'Test Perfume',
'sku' => 'TP001',
'brand_id' => $brand->id,
'cost_price' => 150000,
'sale_price' => 250000,
'concentration' => Concentration::EAU_DE_PERFUME,
'base_notes' => 'Vanilla, Musk',
'middle_notes' => 'Jasmine, Rose',
'top_notes' => 'Bergamot, Lemon',
'description' => 'Test description',
]);
$perfume->categories()->attach([$this->categories->first()->id]);
$perfume->outlets()->attach([$this->outlets->first()->id]);
$component = mountPerfumeEditComponent($this->user, $perfume);
expect($component->form->name)->toBe('Test Perfume');
expect($component->form->sku)->toBe('TP001');
expect($component->form->brand_id)->toBe((string) $brand->id);
expect($component->form->cost_price)->toBe('150000');
expect($component->form->sale_price)->toBe('250000');
expect($component->form->concentration)->toBe((string) Concentration::EAU_DE_PERFUME->value);
expect($component->form->base_notes)->toBe('Vanilla, Musk');
expect($component->form->middle_notes)->toBe('Jasmine, Rose');
expect($component->form->top_notes)->toBe('Bergamot, Lemon');
expect($component->form->description)->toBe('Test description');
expect($component->form->category_ids)->toBe([$this->categories->first()->id]);
expect($component->form->outlet_ids)->toBe([$this->outlets->first()->id]);
});
/*
|--------------------------------------------------------------------------
| Update Perfume Tests
|--------------------------------------------------------------------------
*/
it('updates perfume successfully when authorized', function () {
$updatePermission = Permission::where('name', 'update perfume')->first();
$this->user->givePermissionTo($updatePermission);
$oldBrand = $this->brands->first();
$perfume = Perfume::factory()->create([
'name' => 'Original Perfume',
'sku' => 'OP001',
'brand_id' => $oldBrand->id,
'cost_price' => 100000,
'sale_price' => 200000,
'concentration' => Concentration::EAU_DE_TOILETTE,
]);
$perfume->categories()->attach([$this->categories->first()->id]);
$perfume->outlets()->attach([$this->outlets->first()->id]);
$newBrand = $this->brands->last();
$updateData = [
'name' => 'Updated Perfume',
'sku' => 'UP001',
'brand_id' => $newBrand->id,
'cost_price' => '150000',
'sale_price' => '300000',
'concentration' => Concentration::EAU_DE_PERFUME->value,
'base_notes' => 'Updated Base Notes',
'middle_notes' => 'Updated Middle Notes',
'top_notes' => 'Updated Top Notes',
'description' => 'Updated description',
'category_ids' => [$this->categories->first()->id, $this->categories->last()->id],
'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id],
];
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', $updateData['name'])
->set('form.sku', $updateData['sku'])
->set('form.brand_id', $updateData['brand_id'])
->set('form.cost_price', $updateData['cost_price'])
->set('form.sale_price', $updateData['sale_price'])
->set('form.concentration', $updateData['concentration'])
->set('form.base_notes', $updateData['base_notes'])
->set('form.middle_notes', $updateData['middle_notes'])
->set('form.top_notes', $updateData['top_notes'])
->set('form.description', $updateData['description'])
->set('form.category_ids', $updateData['category_ids'])
->set('form.outlet_ids', $updateData['outlet_ids'])
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.catalog.perfume.index'));
$perfume->refresh();
expect($perfume->name)->toBe($updateData['name']);
expect($perfume->sku)->toBe($updateData['sku']);
expect($perfume->brand_id)->toBe($updateData['brand_id']);
expect($perfume->cost_price)->toBe(150000);
expect($perfume->sale_price)->toBe(300000);
expect($perfume->concentration->value)->toBe($updateData['concentration']);
expect($perfume->base_notes)->toBe($updateData['base_notes']);
expect($perfume->middle_notes)->toBe($updateData['middle_notes']);
expect($perfume->top_notes)->toBe($updateData['top_notes']);
expect($perfume->description)->toBe($updateData['description']);
expect($perfume->categories)->toHaveCount(2);
expect($perfume->outlets)->toHaveCount(2);
});
it('prevents perfume update when unauthorized', function () {
$this->ownerRole->revokePermissionTo('update perfume');
Gate::define('update perfume', fn () => false);
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', 'Updated Name')
->call('save')
->assertForbidden();
});
it('syncs perfume category and outlet relationships correctly on update', function () {
$updatePermission = Permission::where('name', 'update perfume')->first();
$this->user->givePermissionTo($updatePermission);
$perfume = Perfume::factory()->create();
$perfume->categories()->attach([$this->categories->first()->id]);
$perfume->outlets()->attach([$this->outlets->first()->id]);
// Change to different categories and outlets
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', 'Updated Perfume')
->set('form.sku', 'UP001')
->set('form.sale_price', '200000')
->set('form.category_ids', [$this->categories[1]->id, $this->categories[2]->id])
->set('form.outlet_ids', [$this->outlets[1]->id, $this->outlets[2]->id])
->call('save')
->assertHasNoErrors();
$perfume->refresh();
expect($perfume->categories)->toHaveCount(2);
expect($perfume->outlets)->toHaveCount(2);
expect($perfume->categories->pluck('id')->toArray())->toBe([$this->categories[1]->id, $this->categories[2]->id]);
expect($perfume->outlets->pluck('id')->toArray())->toBe([$this->outlets[1]->id, $this->outlets[2]->id]);
});
it('allows same sku on update of same perfume', function () {
$updatePermission = Permission::where('name', 'update perfume')->first();
$this->user->givePermissionTo($updatePermission);
$perfume = Perfume::factory()->create(['sku' => 'EXISTING001']);
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', 'Updated Perfume')
->set('form.sku', 'EXISTING001') // Same SKU as existing perfume
->set('form.sale_price', '200000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasNoErrors();
});
it('updates perfume brand to null', function () {
$updatePermission = Permission::where('name', 'update perfume')->first();
$this->user->givePermissionTo($updatePermission);
$perfume = Perfume::factory()->create(['brand_id' => $this->brands->first()->id]);
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', 'Perfume Without Brand')
->set('form.sku', 'PWB001')
->set('form.brand_id', null) // Remove brand
->set('form.sale_price', '150000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasNoErrors();
$perfume->refresh();
expect($perfume->brand_id)->toBeNull();
});
/*
|--------------------------------------------------------------------------
| Form Validation Tests on Edit
|--------------------------------------------------------------------------
*/
it('validates required name field on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', '')
->call('save')
->assertHasErrors(['form.name']);
});
it('validates name field max length on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', str_repeat('a', 51))
->call('save')
->assertHasErrors(['form.name']);
});
it('validates required sku field on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.sku', '')
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates sku field max length on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.sku', str_repeat('a', 21))
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates sku uniqueness on perfume update with different perfume', function () {
$perfume1 = Perfume::factory()->create(['sku' => 'SKU001']);
$perfume2 = Perfume::factory()->create(['sku' => 'SKU002']);
mountPerfumeEditComponent($this->user, $perfume1)
->set('form.sku', 'SKU002') // Try to use SKU from perfume2
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates brand_id exists if provided on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.brand_id', 999) // Non-existent brand
->call('save')
->assertHasErrors(['form.brand_id']);
});
it('validates required sale_price field on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.sale_price', '')
->call('save')
->assertHasErrors(['form.sale_price']);
});
it('validates concentration is valid enum value on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.concentration', 'invalid')
->call('save')
->assertHasErrors(['form.concentration']);
});
it('validates required category_ids field on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.category_ids', [])
->call('save')
->assertHasErrors(['form.category_ids']);
});
it('validates category_ids contains valid category ids on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.category_ids', [999])
->call('save')
->assertHasErrors(['form.category_ids.*']);
});
it('validates required outlet_ids field on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.outlet_ids', [])
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids contains valid outlet ids on perfume update', function () {
$perfume = Perfume::factory()->create();
mountPerfumeEditComponent($this->user, $perfume)
->set('form.outlet_ids', [999])
->call('save')
->assertHasErrors(['form.outlet_ids.*']);
});
/*
|--------------------------------------------------------------------------
| Real-time Validation Tests on Edit
|--------------------------------------------------------------------------
*/
it('validates perfume name field in real-time on edit', function () {
$perfume = Perfume::factory()->create();
$component = mountPerfumeEditComponent($this->user, $perfume);
// Valid name
$component->set('form.name', 'Valid Updated Perfume Name')
->assertHasNoErrors(['form.name']);
// Empty name
$component->set('form.name', '')
->assertHasErrors(['form.name']);
// Name too long
$component->set('form.name', str_repeat('a', 51))
->assertHasErrors(['form.name']);
});
it('validates perfume sku field in real-time on edit', function () {
$perfume = Perfume::factory()->create();
$component = mountPerfumeEditComponent($this->user, $perfume);
// Valid SKU
$component->set('form.sku', 'VALID001')
->assertHasNoErrors(['form.sku']);
// Empty SKU
$component->set('form.sku', '')
->assertHasErrors(['form.sku']);
// SKU too long
$component->set('form.sku', str_repeat('a', 21))
->assertHasErrors(['form.sku']);
});
it('validates perfume price fields in real-time on edit', function () {
$perfume = Perfume::factory()->create();
$component = mountPerfumeEditComponent($this->user, $perfume);
// Valid sale price
$component->set('form.sale_price', '300000')
->assertHasNoErrors(['form.sale_price']);
// Empty sale price
$component->set('form.sale_price', '')
->assertHasErrors(['form.sale_price']);
// Negative sale price
$component->set('form.sale_price', '-300000')
->assertHasErrors(['form.sale_price']);
});
it('validates perfume concentration field in real-time on edit', function () {
$perfume = Perfume::factory()->create();
$component = mountPerfumeEditComponent($this->user, $perfume);
// Valid concentration
$component->set('form.concentration', Concentration::EAU_DE_PERFUME->value)
->assertHasNoErrors(['form.concentration']);
// Invalid concentration
$component->set('form.concentration', 'invalid')
->assertHasErrors(['form.concentration']);
});
/*
|--------------------------------------------------------------------------
| Integration Tests
|--------------------------------------------------------------------------
*/
it('updates perfume and syncs category and outlet relationships', function () {
$updatePermission = Permission::where('name', 'update perfume')->first();
$this->user->givePermissionTo($updatePermission);
$perfume = Perfume::factory()->create();
$perfume->categories()->attach([$this->categories->first()->id]);
$perfume->outlets()->attach([$this->outlets->first()->id]);
$newCategories = [$this->categories[1]->id, $this->categories[2]->id];
$newOutlets = [$this->outlets[1]->id, $this->outlets[2]->id];
mountPerfumeEditComponent($this->user, $perfume)
->set('form.name', 'Updated Integration Perfume')
->set('form.sku', 'UIP001')
->set('form.sale_price', '400000')
->set('form.category_ids', $newCategories)
->set('form.outlet_ids', $newOutlets)
->call('save')
->assertHasNoErrors();
$perfume->refresh();
expect($perfume->name)->toBe('Updated Integration Perfume');
expect($perfume->categories)->toHaveCount(2);
expect($perfume->outlets)->toHaveCount(2);
expect($perfume->categories->pluck('id')->toArray())->toBe($newCategories);
expect($perfume->outlets->pluck('id')->toArray())->toBe($newOutlets);
});
/*
|--------------------------------------------------------------------------
| Error Handling and Edge Cases
|--------------------------------------------------------------------------
*/
it('handles invalid perfume hash in edit component', function () {
expect(fn () => mountPerfumeEditComponent($this->user, 'invalid-hash'))
->toThrow(\TypeError::class);
});
it('validates perfume form data integrity during operations', function () {
// Test that form data is properly reset between operations
$perfume1 = Perfume::factory()->create(['name' => 'First Perfume']);
$perfume2 = Perfume::factory()->create(['name' => 'Second Perfume']);
$component = mountPerfumeEditComponent($this->user, $perfume1);
// Open modal for first perfume
expect($component->form->name)->toBe('First Perfume');
// Verify first perfume wasn't modified
$perfume1->refresh();
expect($perfume1->name)->toBe('First Perfume');
});