parfum/tests/Feature/Livewire/Studio/Catalog/Bottle/AuditTest.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

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();
});