- kasih return type - ubah array to collection di audit - mengubah waktu refresh tabel menjadi 30 detik
347 lines
12 KiB
PHP
347 lines
12 KiB
PHP
<?php
|
|
|
|
use App\Models\Bottle;
|
|
use App\Models\Employee;
|
|
use App\Models\Outlet;
|
|
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 outlets
|
|
$this->outlets = Outlet::factory()->count(3)->create();
|
|
});
|
|
|
|
function mountBottleCreateComponent(User $user)
|
|
{
|
|
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Bottle\Create::class);
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Create Component Rendering & Mount
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('renders bottle create page successfully', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->assertViewIs('livewire.studio.catalog.bottle.form')
|
|
->assertViewHas('pageTitle', 'Tambah Botol');
|
|
});
|
|
|
|
it('initializes bottle form properties on mount', function () {
|
|
$component = mountBottleCreateComponent($this->user);
|
|
|
|
expect($component->form->name)->toBe('');
|
|
expect($component->form->size)->toBe('');
|
|
expect($component->form->cost_price)->toBe('');
|
|
expect($component->form->sale_price)->toBe('');
|
|
expect($component->form->description)->toBeNull();
|
|
expect($component->form->outlet_ids)->toBe([]);
|
|
expect($component->form->image)->toBe([]);
|
|
expect($component->outlets)->toHaveCount(3);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Create Bottle Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('creates bottle successfully when authorized', function () {
|
|
$data = [
|
|
'name' => 'New Bottle',
|
|
'size' => '100',
|
|
'cost_price' => '50000',
|
|
'sale_price' => '75000',
|
|
'description' => 'Bottle description',
|
|
'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id],
|
|
];
|
|
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', $data['name'])
|
|
->set('form.size', $data['size'])
|
|
->set('form.cost_price', $data['cost_price'])
|
|
->set('form.sale_price', $data['sale_price'])
|
|
->set('form.description', $data['description'])
|
|
->set('form.outlet_ids', $data['outlet_ids'])
|
|
->call('save')
|
|
->assertHasNoErrors()
|
|
->assertRedirect(route('studio.catalog.bottle.index'));
|
|
|
|
expect(Bottle::count())->toBe(1);
|
|
$bottle = Bottle::first();
|
|
expect($bottle->name)->toBe($data['name']);
|
|
expect($bottle->size)->toBe(100);
|
|
expect($bottle->cost_price)->toBe(50000);
|
|
expect($bottle->sale_price)->toBe(75000);
|
|
expect($bottle->point_per_pcs)->toBe(750); // 75000 / 100
|
|
expect($bottle->description)->toBe($data['description']);
|
|
expect($bottle->outlets)->toHaveCount(2);
|
|
});
|
|
|
|
it('prevents bottle creation when unauthorized', function () {
|
|
$this->ownerRole->revokePermissionTo('create bottle');
|
|
Gate::define('create bottle', fn () => false);
|
|
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '50')
|
|
->set('form.sale_price', '25000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('creates bottle 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 bottle')->first();
|
|
$limitedRole->givePermissionTo($createPermission);
|
|
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '30')
|
|
->set('form.cost_price', '') // Empty cost price
|
|
->set('form.sale_price', '20000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$bottle = Bottle::first();
|
|
expect($bottle->cost_price)->toBe(0);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Form Validation Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('validates required name field on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', '')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.name']);
|
|
});
|
|
|
|
it('validates name field max length on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', str_repeat('a', 51))
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.name']);
|
|
});
|
|
|
|
it('validates required size field on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.size']);
|
|
});
|
|
|
|
it('validates size is unsigned integer on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '-50') // Negative value
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.size']);
|
|
});
|
|
|
|
it('validates required sale_price field on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.sale_price']);
|
|
});
|
|
|
|
it('validates sale_price is unsigned integer on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '-50000') // Negative value
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasErrors(['form.sale_price']);
|
|
});
|
|
|
|
it('validates required outlet_ids field on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids']);
|
|
});
|
|
|
|
it('validates outlet_ids contains valid outlet ids on bottle create', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [999])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids.*']);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Real-time Validation Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('validates bottle name field in real-time', function () {
|
|
$component = mountBottleCreateComponent($this->user);
|
|
|
|
// Valid name
|
|
$component->set('form.name', 'Valid Bottle 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 bottle size field in real-time', function () {
|
|
$component = mountBottleCreateComponent($this->user);
|
|
|
|
// Valid size
|
|
$component->set('form.size', '100')
|
|
->assertHasNoErrors(['form.size']);
|
|
|
|
// Empty size
|
|
$component->set('form.size', '')
|
|
->assertHasErrors(['form.size']);
|
|
|
|
// Negative size
|
|
$component->set('form.size', '-50')
|
|
->assertHasErrors(['form.size']);
|
|
});
|
|
|
|
it('validates bottle price fields in real-time', function () {
|
|
$component = mountBottleCreateComponent($this->user);
|
|
|
|
// Valid sale price
|
|
$component->set('form.sale_price', '50000')
|
|
->assertHasNoErrors(['form.sale_price']);
|
|
|
|
// Empty sale price
|
|
$component->set('form.sale_price', '')
|
|
->assertHasErrors(['form.sale_price']);
|
|
|
|
// Negative sale price
|
|
$component->set('form.sale_price', '-50000')
|
|
->assertHasErrors(['form.sale_price']);
|
|
|
|
// Valid cost price
|
|
$component->set('form.cost_price', '30000')
|
|
->assertHasNoErrors(['form.cost_price']);
|
|
|
|
// Negative cost price
|
|
$component->set('form.cost_price', '-30000')
|
|
->assertHasErrors(['form.cost_price']);
|
|
});
|
|
|
|
it('validates bottle outlet_ids in real-time', function () {
|
|
$component = mountBottleCreateComponent($this->user);
|
|
|
|
// Valid outlet IDs
|
|
$component->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->assertHasNoErrors(['form.outlet_ids']);
|
|
|
|
// Empty outlet IDs - should trigger validation on submit
|
|
$component->set('form.outlet_ids', [])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids']);
|
|
|
|
// Invalid outlet ID
|
|
$component = mountBottleCreateComponent($this->user);
|
|
$component->set('form.name', 'Test Bottle')
|
|
->set('form.size', '100')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [999])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids.*']);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Integration Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('creates bottle and attaches to outlets correctly', function () {
|
|
$outlet1 = $this->outlets->first();
|
|
$outlet2 = $this->outlets->last();
|
|
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Integration Test Bottle')
|
|
->set('form.size', '200')
|
|
->set('form.cost_price', '75000') // Required for Owner role
|
|
->set('form.sale_price', '100000')
|
|
->set('form.outlet_ids', [$outlet1->id, $outlet2->id])
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$bottle = Bottle::where('size', 200)->first();
|
|
expect($bottle)->not->toBeNull();
|
|
expect($bottle->outlets)->toHaveCount(2);
|
|
expect($bottle->outlets->pluck('id')->toArray())->toContain($outlet1->id, $outlet2->id);
|
|
});
|
|
|
|
it('handles large numbers in bottle price fields', function () {
|
|
mountBottleCreateComponent($this->user)
|
|
->set('form.name', 'Expensive Bottle')
|
|
->set('form.size', '500')
|
|
->set('form.cost_price', '5000000') // 5 million
|
|
->set('form.sale_price', '7500000') // 7.5 million
|
|
->set('form.outlet_ids', [$this->outlets->first()->id])
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$bottle = Bottle::where('size', 500)->first();
|
|
expect($bottle->cost_price)->toBe(5000000);
|
|
expect($bottle->sale_price)->toBe(7500000);
|
|
expect($bottle->point_per_pcs)->toBe(75000); // 7500000 / 100
|
|
});
|