- kasih return type - ubah array to collection di audit - mengubah waktu refresh tabel menjadi 30 detik
467 lines
16 KiB
PHP
467 lines
16 KiB
PHP
<?php
|
|
|
|
use App\Enums\AuditStockStatus;
|
|
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 Livewire\Livewire;
|
|
use Spatie\Activitylog\Models\Activity;
|
|
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 mountBottleAuditComponent(User $user, Outlet $outlet, Bottle $bottle)
|
|
{
|
|
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Bottle\Audit::class, ['outlet' => $outlet, 'bottle' => $bottle]);
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Audit Component Rendering & Mount
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('bottle audit component initializes correctly', function () {
|
|
$bottle = Bottle::factory()->create(['name' => 'Test Bottle']);
|
|
$outlet = $this->outlets->first();
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->label)->toBe('Test Bottle di '.$outlet->name);
|
|
expect($component->activityLogs)->toBeInstanceOf(\Illuminate\Database\Eloquent\Collection::class);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Activity Logs Display Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('displays bottle activity logs correctly', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
// Create some activity logs with expected properties
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock updated',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 5,
|
|
'quantity_change' => 10,
|
|
'new_stock' => 15,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock decreased',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 15,
|
|
'quantity_change' => -5,
|
|
'new_stock' => 10,
|
|
],
|
|
'event' => AuditStockStatus::DECREASED->value,
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(2);
|
|
expect($component->activityLogs->first()->event['label'])->toBe(AuditStockStatus::INCREASED->label());
|
|
expect($component->activityLogs->first()->event['color'])->toBe(AuditStockStatus::INCREASED->color());
|
|
expect($component->activityLogs->last()->event['label'])->toBe(AuditStockStatus::DECREASED->label());
|
|
expect($component->activityLogs->last()->event['color'])->toBe(AuditStockStatus::DECREASED->color());
|
|
});
|
|
|
|
it('filters bottle activity logs by outlet', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet1 = $this->outlets->first();
|
|
$outlet2 = $this->outlets->last();
|
|
|
|
// Create activity for outlet1
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock updated outlet1',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet1->id,
|
|
'previous_stock' => 5,
|
|
'quantity_change' => 10,
|
|
'new_stock' => 15,
|
|
],
|
|
'event' => AuditStockStatus::UPDATED->value,
|
|
]);
|
|
|
|
// Create activity for outlet2
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock increased outlet2',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet2->id,
|
|
'previous_stock' => 0,
|
|
'quantity_change' => 20,
|
|
'new_stock' => 20,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
$component1 = mountBottleAuditComponent($this->user, $outlet1, $bottle);
|
|
$component2 = mountBottleAuditComponent($this->user, $outlet2, $bottle);
|
|
|
|
expect($component1->activityLogs)->toHaveCount(1);
|
|
expect($component1->activityLogs->first()->description)->toBe('Stock updated outlet1');
|
|
|
|
expect($component2->activityLogs)->toHaveCount(1);
|
|
expect($component2->activityLogs->first()->description)->toBe('Stock increased outlet2');
|
|
});
|
|
|
|
it('orders bottle activity logs by latest first', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
// Create activities with different timestamps
|
|
$activity1 = Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'first activity',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 5,
|
|
'quantity_change' => 5,
|
|
'new_stock' => 10,
|
|
],
|
|
'event' => AuditStockStatus::UPDATED->value,
|
|
'created_at' => now()->subMinutes(5),
|
|
]);
|
|
|
|
$activity2 = Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'second activity',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 10,
|
|
'quantity_change' => 10,
|
|
'new_stock' => 20,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(2);
|
|
expect($component->activityLogs->first()->description)->toBe('second activity');
|
|
expect($component->activityLogs->last()->description)->toBe('first activity');
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Activity Logs Content Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('maps bottle activity events to correct labels and colors', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
// Test INCREASED event
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock increased',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 10,
|
|
'quantity_change' => 5,
|
|
'new_stock' => 15,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
// Test DECREASED event
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock decreased',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 15,
|
|
'quantity_change' => -3,
|
|
'new_stock' => 12,
|
|
],
|
|
'event' => AuditStockStatus::DECREASED->value,
|
|
]);
|
|
|
|
// Test UPDATED event
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock updated',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 12,
|
|
'quantity_change' => 0,
|
|
'new_stock' => 12,
|
|
],
|
|
'event' => AuditStockStatus::UPDATED->value,
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(3);
|
|
|
|
// Check INCREASED event mapping
|
|
$increasedLog = collect($component->activityLogs)->firstWhere('event.label', AuditStockStatus::INCREASED->label());
|
|
expect($increasedLog['event']['color'])->toBe(AuditStockStatus::INCREASED->color());
|
|
|
|
// Check DECREASED event mapping
|
|
$decreasedLog = collect($component->activityLogs)->firstWhere('event.label', AuditStockStatus::DECREASED->label());
|
|
expect($decreasedLog['event']['color'])->toBe(AuditStockStatus::DECREASED->color());
|
|
|
|
// Check UPDATED event mapping
|
|
$updatedLog = collect($component->activityLogs)->firstWhere('event.label', AuditStockStatus::UPDATED->label());
|
|
expect($updatedLog['event']['color'])->toBe(AuditStockStatus::UPDATED->color());
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Empty State Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('handles empty bottle activity logs in audit component', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toBeInstanceOf(\Illuminate\Database\Eloquent\Collection::class);
|
|
expect($component->activityLogs)->toHaveCount(0);
|
|
});
|
|
|
|
it('handles bottle with no activities in specific outlet', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet1 = $this->outlets->first();
|
|
$outlet2 = $this->outlets->last();
|
|
|
|
// Create activity only for outlet2
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock updated',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet2->id,
|
|
'previous_stock' => 5,
|
|
'quantity_change' => 5,
|
|
'new_stock' => 10,
|
|
],
|
|
'event' => AuditStockStatus::UPDATED->value,
|
|
]);
|
|
|
|
$component1 = mountBottleAuditComponent($this->user, $outlet1, $bottle);
|
|
$component2 = mountBottleAuditComponent($this->user, $outlet2, $bottle);
|
|
|
|
expect($component1->activityLogs)->toHaveCount(0);
|
|
expect($component2->activityLogs)->toHaveCount(1);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Integration Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('displays real bottle activity log data correctly', function () {
|
|
$bottle = Bottle::factory()->create(['name' => 'Integration Bottle']);
|
|
$outlet = $this->outlets->first();
|
|
|
|
$activity = Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Stock adjustment for integration test',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 100,
|
|
'quantity_change' => 25,
|
|
'new_stock' => 125,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(1);
|
|
|
|
$log = $component->activityLogs->first();
|
|
expect($log->description)->toBe('Stock adjustment for integration test');
|
|
expect($log->properties['previous_stock'])->toBe(100);
|
|
expect($log->properties['quantity_change'])->toBe(25);
|
|
expect($log->properties['new_stock'])->toBe(125);
|
|
expect($log->event['label'])->toBe(AuditStockStatus::INCREASED->label());
|
|
expect($log->event['color'])->toBe(AuditStockStatus::INCREASED->color());
|
|
});
|
|
|
|
it('handles multiple bottle activities across different outlets', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet1 = $this->outlets->first();
|
|
$outlet2 = $this->outlets->last();
|
|
|
|
// Create activities for both outlets
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Activity in outlet 1',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet1->id,
|
|
'previous_stock' => 10,
|
|
'quantity_change' => 5,
|
|
'new_stock' => 15,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Activity in outlet 2',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet2->id,
|
|
'previous_stock' => 20,
|
|
'quantity_change' => -3,
|
|
'new_stock' => 17,
|
|
],
|
|
'event' => AuditStockStatus::DECREASED->value,
|
|
]);
|
|
|
|
$component1 = mountBottleAuditComponent($this->user, $outlet1, $bottle);
|
|
$component2 = mountBottleAuditComponent($this->user, $outlet2, $bottle);
|
|
|
|
expect($component1->activityLogs)->toHaveCount(1);
|
|
expect($component1->activityLogs->first()->description)->toBe('Activity in outlet 1');
|
|
|
|
expect($component2->activityLogs)->toHaveCount(1);
|
|
expect($component2->activityLogs->first()->description)->toBe('Activity in outlet 2');
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Edge Cases and Error Handling
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
it('handles bottle activity logs with missing properties gracefully', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
// Create activity with incomplete properties (shouldn't happen in real usage)
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Incomplete activity',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => get_class($this->user),
|
|
'causer_id' => $this->user->id,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 0, // Add required properties
|
|
'quantity_change' => 0,
|
|
'new_stock' => 0,
|
|
],
|
|
'event' => AuditStockStatus::UPDATED->value,
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(1);
|
|
// Component should render correctly with all required properties
|
|
});
|
|
|
|
it('handles bottle activities with null causer', function () {
|
|
$bottle = Bottle::factory()->create();
|
|
$outlet = $this->outlets->first();
|
|
|
|
Activity::create([
|
|
'log_name' => 'default',
|
|
'description' => 'Activity with no causer',
|
|
'subject_type' => get_class($bottle),
|
|
'subject_id' => $bottle->id,
|
|
'causer_type' => null,
|
|
'causer_id' => null,
|
|
'properties' => [
|
|
'outlet_id' => $outlet->id,
|
|
'previous_stock' => 5,
|
|
'quantity_change' => 5,
|
|
'new_stock' => 10,
|
|
],
|
|
'event' => AuditStockStatus::INCREASED->value,
|
|
]);
|
|
|
|
$component = mountBottleAuditComponent($this->user, $outlet, $bottle);
|
|
|
|
expect($component->activityLogs)->toHaveCount(1);
|
|
expect($component->activityLogs->first()->causer)->toBeNull();
|
|
});
|