481 lines
17 KiB
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');
|
|
});
|