parfum/tests/Feature/Livewire/Studio/Catalog/Perfume/CreateTest.php
Yoga Pangestu 81a9753620 feat(perfume. bottle): implementasi testing
- kasih return type
- ubah array to collection di audit
- mengubah waktu refresh tabel menjadi 30 detik
2025-12-11 11:15:59 +07:00

458 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 mountPerfumeCreateComponent(User $user)
{
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Perfume\Create::class);
}
/*
|--------------------------------------------------------------------------
| Create Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders perfume create page successfully', function () {
mountPerfumeCreateComponent($this->user)
->assertViewIs('livewire.studio.catalog.perfume.form')
->assertViewHas('pageTitle', 'Tambah Parfum');
});
it('initializes perfume form properties on mount', function () {
$component = mountPerfumeCreateComponent($this->user);
expect($component->form->name)->toBe('');
expect($component->form->sku)->toBe('');
expect($component->form->brand_id)->toBeNull();
expect($component->form->cost_price)->toBe('');
expect($component->form->sale_price)->toBe('');
expect($component->form->concentration)->toBe('2');
expect($component->form->base_notes)->toBeNull();
expect($component->form->middle_notes)->toBeNull();
expect($component->form->top_notes)->toBeNull();
expect($component->form->description)->toBeNull();
expect($component->form->category_ids)->toBe([]);
expect($component->form->outlet_ids)->toBe([]);
expect($component->form->image)->toBe([]);
expect($component->outlets)->toHaveCount(3);
expect($component->brands)->toHaveCount(2);
expect($component->categories)->toHaveCount(3);
});
/*
|--------------------------------------------------------------------------
| Create Perfume Tests
|--------------------------------------------------------------------------
*/
it('creates perfume successfully when authorized', function () {
$data = [
'name' => 'New Perfume',
'sku' => 'NP001',
'brand_id' => $this->brands->first()->id,
'cost_price' => '150000',
'sale_price' => '250000',
'concentration' => Concentration::EAU_DE_PERFUME->value,
'base_notes' => 'Vanilla, Musk',
'middle_notes' => 'Jasmine, Rose',
'top_notes' => 'Bergamot, Lemon',
'description' => 'A beautiful fragrance',
'category_ids' => [$this->categories->first()->id, $this->categories->last()->id],
'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id],
];
mountPerfumeCreateComponent($this->user)
->set('form.name', $data['name'])
->set('form.sku', $data['sku'])
->set('form.brand_id', $data['brand_id'])
->set('form.cost_price', $data['cost_price'])
->set('form.sale_price', $data['sale_price'])
->set('form.concentration', $data['concentration'])
->set('form.base_notes', $data['base_notes'])
->set('form.middle_notes', $data['middle_notes'])
->set('form.top_notes', $data['top_notes'])
->set('form.description', $data['description'])
->set('form.category_ids', $data['category_ids'])
->set('form.outlet_ids', $data['outlet_ids'])
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.catalog.perfume.index'));
expect(Perfume::count())->toBe(1);
$perfume = Perfume::first();
expect($perfume->name)->toBe($data['name']);
expect($perfume->sku)->toBe($data['sku']);
expect($perfume->brand_id)->toBe($data['brand_id']);
expect($perfume->cost_price)->toBe(150000);
expect($perfume->sale_price)->toBe(250000);
expect($perfume->point_per_ml)->toBe(250); // 250000 / 1000
expect($perfume->concentration->value)->toBe($data['concentration']);
expect($perfume->base_notes)->toBe($data['base_notes']);
expect($perfume->middle_notes)->toBe($data['middle_notes']);
expect($perfume->top_notes)->toBe($data['top_notes']);
expect($perfume->description)->toBe($data['description']);
expect($perfume->categories)->toHaveCount(2);
expect($perfume->outlets)->toHaveCount(2);
});
it('prevents perfume creation when unauthorized', function () {
$this->ownerRole->revokePermissionTo('create perfume');
Gate::define('create perfume', fn () => false);
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertForbidden();
});
it('creates perfume with empty cost_price for non-owner roles', function () {
// Create user with limited role
$this->user->roles()->detach();
$limitedRole = Role::create(['name' => 'Cashier']);
$this->user->roles()->attach($limitedRole->id);
$createPermission = Permission::where('name', 'create perfume')->first();
$limitedRole->givePermissionTo($createPermission);
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.cost_price', '') // Empty cost price
->set('form.sale_price', '200000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasNoErrors();
$perfume = Perfume::first();
expect($perfume->cost_price)->toBe(0);
});
it('creates perfume with optional brand', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Perfume Without Brand')
->set('form.sku', 'PWB001')
->set('form.brand_id', null) // No brand
->set('form.cost_price', '100000') // Required for Owner role
->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 = Perfume::first();
expect($perfume->brand_id)->toBeNull();
});
/*
|--------------------------------------------------------------------------
| Form Validation Tests
|--------------------------------------------------------------------------
*/
it('validates required name field on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', '')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.name']);
});
it('validates name field max length on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', str_repeat('a', 51))
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.name']);
});
it('validates required sku field on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', '')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates sku field max length on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', str_repeat('a', 21))
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates sku uniqueness on perfume create', function () {
Perfume::factory()->create(['sku' => 'EXISTING001']);
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'EXISTING001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.sku']);
});
it('validates brand_id exists if provided', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.brand_id', 999) // Non-existent brand
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.brand_id']);
});
it('validates required sale_price field on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.sale_price']);
});
it('validates concentration is valid enum value', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.concentration', 'invalid')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.concentration']);
});
it('validates required category_ids field on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.category_ids']);
});
it('validates category_ids contains valid category ids', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [999])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.category_ids.*']);
});
it('validates required outlet_ids field on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [])
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids contains valid outlet ids on perfume create', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [999])
->call('save')
->assertHasErrors(['form.outlet_ids.*']);
});
it('validates notes fields max length', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Test Perfume')
->set('form.sku', 'TP001')
->set('form.sale_price', '100000')
->set('form.base_notes', str_repeat('a', 256)) // Exceeds 255
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasErrors(['form.base_notes']);
});
/*
|--------------------------------------------------------------------------
| Real-time Validation Tests
|--------------------------------------------------------------------------
*/
it('validates perfume name field in real-time', function () {
$component = mountPerfumeCreateComponent($this->user);
// Valid name
$component->set('form.name', 'Valid 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', function () {
$component = mountPerfumeCreateComponent($this->user);
// 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']);
// Duplicate SKU
Perfume::factory()->create(['sku' => 'EXISTING001']);
$component->set('form.sku', 'EXISTING001')
->assertHasErrors(['form.sku']);
});
it('validates perfume price fields in real-time', function () {
$component = mountPerfumeCreateComponent($this->user);
// Valid sale price
$component->set('form.sale_price', '200000')
->assertHasNoErrors(['form.sale_price']);
// Empty sale price
$component->set('form.sale_price', '')
->assertHasErrors(['form.sale_price']);
// Negative sale price
$component->set('form.sale_price', '-200000')
->assertHasErrors(['form.sale_price']);
});
it('validates perfume concentration field in real-time', function () {
$component = mountPerfumeCreateComponent($this->user);
// 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('creates perfume and attaches to categories and outlets correctly', function () {
$category1 = $this->categories->first();
$category2 = $this->categories->last();
$outlet1 = $this->outlets->first();
$outlet2 = $this->outlets->last();
$brand = $this->brands->first();
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Integration Test Perfume')
->set('form.sku', 'ITP001')
->set('form.brand_id', $brand->id)
->set('form.cost_price', '200000') // Required for Owner role
->set('form.sale_price', '350000')
->set('form.concentration', Concentration::EAU_DE_PERFUME->value)
->set('form.category_ids', [$category1->id, $category2->id])
->set('form.outlet_ids', [$outlet1->id, $outlet2->id])
->call('save')
->assertHasNoErrors();
$perfume = Perfume::where('sku', 'ITP001')->first();
expect($perfume)->not->toBeNull();
expect($perfume->brand->id)->toBe($brand->id);
expect($perfume->categories)->toHaveCount(2);
expect($perfume->outlets)->toHaveCount(2);
expect($perfume->categories->pluck('id')->toArray())->toContain($category1->id, $category2->id);
expect($perfume->outlets->pluck('id')->toArray())->toContain($outlet1->id, $outlet2->id);
});
it('handles large numbers in perfume price fields', function () {
mountPerfumeCreateComponent($this->user)
->set('form.name', 'Expensive Perfume')
->set('form.sku', 'EP001')
->set('form.cost_price', '10000000') // 10 million
->set('form.sale_price', '15000000') // 15 million
->set('form.category_ids', [$this->categories->first()->id])
->set('form.outlet_ids', [$this->outlets->first()->id])
->call('save')
->assertHasNoErrors();
$perfume = Perfume::where('sku', 'EP001')->first();
expect($perfume->cost_price)->toBe(10000000);
expect($perfume->sale_price)->toBe(15000000);
expect($perfume->point_per_ml)->toBe(15000); // 15000000 / 1000
});