- kasih return type - ubah array to collection di audit - mengubah waktu refresh tabel menjadi 30 detik
346 lines
11 KiB
PHP
346 lines
11 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 mountBottleEditComponent(User $user, Bottle $bottle)
|
|
{
|
|
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Bottle\Edit::class, ['bottle' => $bottle->hash]);
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Edit Component Rendering & Mount
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('renders bottle edit page successfully', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->assertViewIs('livewire.studio.catalog.bottle.form')
|
|
->assertViewHas('pageTitle', 'Ubah Botol');
|
|
});
|
|
|
|
it('populates bottle form with bottle data on mount', function () {
|
|
$bottle = Bottle::factory()->create([
|
|
'name' => 'Test Bottle',
|
|
'size' => 100,
|
|
'cost_price' => 50000,
|
|
'sale_price' => 75000,
|
|
'description' => 'Test description',
|
|
]);
|
|
|
|
$bottle->outlets()->attach([$this->outlets->first()->id]);
|
|
|
|
$component = mountBottleEditComponent($this->user, $bottle);
|
|
|
|
expect($component->form->name)->toBe('Test Bottle');
|
|
expect($component->form->size)->toBe('100');
|
|
expect($component->form->cost_price)->toBe('50000');
|
|
expect($component->form->sale_price)->toBe('75000');
|
|
expect($component->form->description)->toBe('Test description');
|
|
expect($component->form->outlet_ids)->toBe([$this->outlets->first()->id]);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Update Bottle Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('updates bottle successfully when authorized', function () {
|
|
$updatePermission = Permission::where('name', 'update bottle')->first();
|
|
$this->user->givePermissionTo($updatePermission);
|
|
|
|
$bottle = Bottle::factory()->create([
|
|
'name' => 'Original Bottle',
|
|
'size' => 50,
|
|
'cost_price' => 25000,
|
|
'sale_price' => 50000,
|
|
]);
|
|
|
|
$bottle->outlets()->attach([$this->outlets->first()->id]);
|
|
|
|
$updateData = [
|
|
'name' => 'Updated Bottle',
|
|
'size' => '100',
|
|
'cost_price' => '40000',
|
|
'sale_price' => '60000',
|
|
'description' => 'Updated description',
|
|
'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id],
|
|
];
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', $updateData['name'])
|
|
->set('form.size', $updateData['size'])
|
|
->set('form.cost_price', $updateData['cost_price'])
|
|
->set('form.sale_price', $updateData['sale_price'])
|
|
->set('form.description', $updateData['description'])
|
|
->set('form.outlet_ids', $updateData['outlet_ids'])
|
|
->call('save')
|
|
->assertHasNoErrors()
|
|
->assertRedirect(route('studio.catalog.bottle.index'));
|
|
|
|
$bottle->refresh();
|
|
expect($bottle->name)->toBe($updateData['name']);
|
|
expect($bottle->size)->toBe(100);
|
|
expect($bottle->cost_price)->toBe(40000);
|
|
expect($bottle->sale_price)->toBe(60000);
|
|
expect($bottle->point_per_pcs)->toBe(600); // 60000 / 100
|
|
expect($bottle->description)->toBe($updateData['description']);
|
|
expect($bottle->outlets)->toHaveCount(2);
|
|
});
|
|
|
|
it('prevents bottle update when unauthorized', function () {
|
|
$this->ownerRole->revokePermissionTo('update bottle');
|
|
Gate::define('update bottle', fn () => false);
|
|
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', 'Updated Name')
|
|
->call('save')
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('syncs bottle outlet relationships correctly on update', function () {
|
|
$updatePermission = Permission::where('name', 'update bottle')->first();
|
|
$this->user->givePermissionTo($updatePermission);
|
|
|
|
$bottle = Bottle::factory()->create();
|
|
$bottle->outlets()->attach([$this->outlets->first()->id]);
|
|
|
|
// Change to different outlets
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', 'Updated Bottle')
|
|
->set('form.size', '200')
|
|
->set('form.sale_price', '50000')
|
|
->set('form.outlet_ids', [$this->outlets[1]->id, $this->outlets[2]->id])
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$bottle->refresh();
|
|
expect($bottle->outlets)->toHaveCount(2);
|
|
expect($bottle->outlets->pluck('id')->toArray())->toBe([$this->outlets[1]->id, $this->outlets[2]->id]);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Form Validation Tests on Edit
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('validates required name field on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', '')
|
|
->call('save')
|
|
->assertHasErrors(['form.name']);
|
|
});
|
|
|
|
it('validates name field max length on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', str_repeat('a', 51))
|
|
->call('save')
|
|
->assertHasErrors(['form.name']);
|
|
});
|
|
|
|
it('validates required size field on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.size', '')
|
|
->call('save')
|
|
->assertHasErrors(['form.size']);
|
|
});
|
|
|
|
it('validates size is unsigned integer on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.size', '-100')
|
|
->call('save')
|
|
->assertHasErrors(['form.size']);
|
|
});
|
|
|
|
it('validates required sale_price field on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.sale_price', '')
|
|
->call('save')
|
|
->assertHasErrors(['form.sale_price']);
|
|
});
|
|
|
|
it('validates sale_price is unsigned integer on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.sale_price', '-50000')
|
|
->call('save')
|
|
->assertHasErrors(['form.sale_price']);
|
|
});
|
|
|
|
it('validates required outlet_ids field on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.outlet_ids', [])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids']);
|
|
});
|
|
|
|
it('validates outlet_ids contains valid outlet ids on bottle update', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.outlet_ids', [999])
|
|
->call('save')
|
|
->assertHasErrors(['form.outlet_ids.*']);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Real-time Validation Tests on Edit
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('validates bottle name field in real-time on edit', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
$component = mountBottleEditComponent($this->user, $bottle);
|
|
|
|
// Valid name
|
|
$component->set('form.name', 'Valid Updated 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 on edit', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
$component = mountBottleEditComponent($this->user, $bottle);
|
|
|
|
// Valid size
|
|
$component->set('form.size', '150')
|
|
->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 on edit', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
|
|
$component = mountBottleEditComponent($this->user, $bottle);
|
|
|
|
// Valid sale price
|
|
$component->set('form.sale_price', '75000')
|
|
->assertHasNoErrors(['form.sale_price']);
|
|
|
|
// Empty sale price
|
|
$component->set('form.sale_price', '')
|
|
->assertHasErrors(['form.sale_price']);
|
|
|
|
// Negative sale price
|
|
$component->set('form.sale_price', '-75000')
|
|
->assertHasErrors(['form.sale_price']);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Integration Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('updates bottle and syncs outlet relationships', function () {
|
|
$updatePermission = Permission::where('name', 'update bottle')->first();
|
|
$this->user->givePermissionTo($updatePermission);
|
|
|
|
$bottle = Bottle::factory()->create();
|
|
$bottle->outlets()->attach([$this->outlets->first()->id]);
|
|
|
|
$newOutlets = [$this->outlets[1]->id, $this->outlets[2]->id];
|
|
|
|
mountBottleEditComponent($this->user, $bottle)
|
|
->set('form.name', 'Updated Integration Bottle')
|
|
->set('form.size', '300')
|
|
->set('form.sale_price', '90000')
|
|
->set('form.outlet_ids', $newOutlets)
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$bottle->refresh();
|
|
expect($bottle->name)->toBe('Updated Integration Bottle');
|
|
expect($bottle->size)->toBe(300);
|
|
expect($bottle->outlets)->toHaveCount(2);
|
|
expect($bottle->outlets->pluck('id')->toArray())->toBe($newOutlets);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Error Handling and Edge Cases
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('handles invalid bottle hash in edit component', function () {
|
|
expect(fn () => mountBottleEditComponent($this->user, 'invalid-hash'))
|
|
->toThrow(\TypeError::class);
|
|
});
|
|
|
|
it('validates bottle form data integrity during operations', function () {
|
|
// Test that form data is properly reset between operations
|
|
$bottle1 = Bottle::factory()->create(['name' => 'First Bottle']);
|
|
$bottle2 = Bottle::factory()->create(['name' => 'Second Bottle']);
|
|
|
|
$component = mountBottleEditComponent($this->user, $bottle1);
|
|
|
|
// Open modal for first bottle
|
|
expect($component->form->name)->toBe('First Bottle');
|
|
|
|
// Verify first bottle wasn't modified
|
|
$bottle1->refresh();
|
|
expect($bottle1->name)->toBe('First Bottle');
|
|
});
|