- kasih return type - ubah array to collection di audit - mengubah waktu refresh tabel menjadi 30 detik
458 lines
17 KiB
PHP
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
|
|
});
|