test: Add feature tests for Location and SubLocation resources to validate CRUD operations and permission handling.

This commit is contained in:
Yoga Pangestu 2026-03-17 22:54:46 +07:00
parent 229ba24fef
commit 84cd57e5dc
2 changed files with 432 additions and 0 deletions

View File

@ -0,0 +1,214 @@
<?php
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Resources\Master\Locations\Pages\ManageLocations;
use App\Models\Location;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create(['is_active' => IsActive::ACTIVE]);
// Setup Permissions
Permission::firstOrCreate(['name' => 'ViewAny:Location', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Create:Location', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Update:Location', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Delete:Location', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Restore:Location', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'ForceDelete:Location', 'guard_name' => 'web']);
$this->user->givePermissionTo([
'ViewAny:Location',
'Create:Location',
'Update:Location',
'Delete:Location',
'Restore:Location',
'ForceDelete:Location',
]);
});
/*
|--------------------------------------------------------------------------
| Positive Scenarios
|--------------------------------------------------------------------------
*/
test('can render location list page', function () {
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->assertSuccessful();
});
test('can list locations', function () {
$locations = Location::factory()->count(5)->create();
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->call('loadTable')
->assertCanSeeTableRecords($locations);
});
test('can create location', function () {
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->mountAction('create')
->setActionData([
'name' => 'Lokus Baru',
])
->callMountedAction()
->assertHasNoActionErrors();
$this->assertDatabaseHas('locations', [
'name' => 'Lokus Baru',
'is_active' => IsActive::ACTIVE->value,
]);
});
test('can edit location', function () {
$location = Location::factory()->create();
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->mountTableAction('edit', $location)
->setActionData([
'name' => 'Lokus Updated',
])
->callMountedTableAction()
->assertHasNoActionErrors();
expect($location->refresh()->name)->toBe('Lokus Updated');
});
test('can toggle is_active status', function () {
$location = Location::factory()->create(['is_active' => IsActive::INACTIVE]);
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->call('updateTableColumnState', 'is_active', $location->getKey(), true);
expect($location->refresh()->is_active)->toBe(IsActive::ACTIVE);
});
/*
|--------------------------------------------------------------------------
| Negative Scenarios (Validation & Authorization)
|--------------------------------------------------------------------------
*/
test('cannot create location with empty name', function () {
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->mountAction('create')
->setActionData(['name' => ''])
->callMountedAction()
->assertHasActionErrors(['name' => 'required']);
});
test('cannot create location with name exceeding 50 characters', function () {
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->mountAction('create')
->setActionData(['name' => str_repeat('a', 51)])
->callMountedAction()
->assertHasActionErrors(['name' => 'max']);
});
test('unauthorized user cannot render location page', function () {
$unauthorizedUser = User::factory()->create(['is_active' => IsActive::ACTIVE]);
$this->actingAs($unauthorizedUser);
Livewire::test(ManageLocations::class)
->assertForbidden();
});
test('user without create permission cannot see create action', function () {
$viewOnlyUser = User::factory()->create(['is_active' => IsActive::ACTIVE]);
$viewOnlyUser->givePermissionTo('ViewAny:Location');
$location = Location::factory()->create();
$this->actingAs($viewOnlyUser);
Livewire::test(ManageLocations::class)
->assertActionHidden('create')
->assertTableActionHidden('edit', $location);
});
/*
|--------------------------------------------------------------------------
| Soft Delete Scenarios
|--------------------------------------------------------------------------
*/
test('can delete location', function () {
$location = Location::factory()->create();
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->callTableAction('delete', $location);
$this->assertSoftDeleted($location);
});
test('can restore deleted location', function () {
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
$location = Location::factory()->create(['deleted_at' => now()]);
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->filterTable('trashed', 'with')
->callTableAction('restore', $location);
$this->assertDatabaseHas('locations', [
'id' => $location->id,
'deleted_at' => null,
]);
});
test('can force delete location', function () {
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
$location = Location::factory()->create(['deleted_at' => now()]);
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->filterTable('trashed', 'only')
->callTableAction('forceDelete', $location);
$this->assertDatabaseMissing('locations', ['id' => $location->id]);
});
test('trashed filter visibility depends on developer role', function () {
$roleAdmin = Role::firstOrCreate(['name' => RoleEnum::ADMINISTRATOR->value, 'guard_name' => 'web']);
$this->user->assignRole($roleAdmin);
$this->actingAs($this->user);
Livewire::test(ManageLocations::class)
->assertTableFilterHidden('trashed');
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
Livewire::test(ManageLocations::class)
->assertTableFilterVisible('trashed');
});

View File

@ -0,0 +1,218 @@
<?php
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Resources\Master\SubLocations\Pages\ManageSubLocations;
use App\Models\Location;
use App\Models\SubLocation;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create(['is_active' => IsActive::ACTIVE]);
// Setup Permissions
Permission::firstOrCreate(['name' => 'ViewAny:SubLocation', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Create:SubLocation', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Update:SubLocation', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Delete:SubLocation', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'Restore:SubLocation', 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => 'ForceDelete:SubLocation', 'guard_name' => 'web']);
$this->user->givePermissionTo([
'ViewAny:SubLocation',
'Create:SubLocation',
'Update:SubLocation',
'Delete:SubLocation',
'Restore:SubLocation',
'ForceDelete:SubLocation',
]);
});
/*
|--------------------------------------------------------------------------
| Positive Scenarios
|--------------------------------------------------------------------------
*/
test('can render sub-location list page', function () {
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->assertSuccessful();
});
test('can list sub-locations', function () {
$subLocations = SubLocation::factory()->count(5)->create();
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->call('loadTable')
->assertCanSeeTableRecords($subLocations);
});
test('can create sub-location', function () {
$location = Location::factory()->create(['is_active' => IsActive::ACTIVE]);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->mountAction('create')
->setActionData([
'location_id' => $location->id,
'name' => 'Sub Lokus Baru',
])
->callMountedAction()
->assertHasNoActionErrors();
$this->assertDatabaseHas('sub_locations', [
'location_id' => $location->id,
'name' => 'Sub Lokus Baru',
]);
});
test('can edit sub-location', function () {
$subLocation = SubLocation::factory()->create();
$newLocation = Location::factory()->create(['is_active' => IsActive::ACTIVE]);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->mountTableAction('edit', $subLocation)
->setActionData([
'location_id' => $newLocation->id,
'name' => 'Sub Lokus Updated',
])
->callMountedTableAction()
->assertHasNoActionErrors();
expect($subLocation->refresh()->name)->toBe('Sub Lokus Updated');
expect($subLocation->location_id)->toBe($newLocation->id);
});
test('can toggle is_active status', function () {
$subLocation = SubLocation::factory()->create(['is_active' => IsActive::INACTIVE]);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->call('updateTableColumnState', 'is_active', $subLocation->getKey(), true);
expect($subLocation->refresh()->is_active)->toBe(IsActive::ACTIVE);
});
/*
|--------------------------------------------------------------------------
| Negative Scenarios (Validation & Authorization)
|--------------------------------------------------------------------------
*/
test('cannot create sub-location with invalid data', function () {
$this->actingAs($this->user);
// Empty name
Livewire::test(ManageSubLocations::class)
->mountAction('create')
->setActionData(['name' => '', 'location_id' => null])
->callMountedAction()
->assertHasActionErrors(['name' => 'required', 'location_id' => 'required']);
// Non-existent location
Livewire::test(ManageSubLocations::class)
->mountAction('create')
->setActionData(['name' => 'Valid Name', 'location_id' => 999999])
->callMountedAction()
->assertHasActionErrors(['location_id']);
});
test('unauthorized user cannot render sub-location page', function () {
$unauthorizedUser = User::factory()->create(['is_active' => IsActive::ACTIVE]);
$this->actingAs($unauthorizedUser);
Livewire::test(ManageSubLocations::class)
->assertForbidden();
});
test('user without update permission cannot see edit action', function () {
$viewOnlyUser = User::factory()->create(['is_active' => IsActive::ACTIVE]);
$viewOnlyUser->givePermissionTo('ViewAny:SubLocation');
$subLocation = SubLocation::factory()->create();
$this->actingAs($viewOnlyUser);
Livewire::test(ManageSubLocations::class)
->assertTableActionHidden('edit', $subLocation);
});
/*
|--------------------------------------------------------------------------
| Soft Delete Scenarios
|--------------------------------------------------------------------------
*/
test('can delete sub-location', function () {
$subLocation = SubLocation::factory()->create();
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->callTableAction('delete', $subLocation);
$this->assertSoftDeleted($subLocation);
});
test('can restore deleted sub-location', function () {
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
$subLocation = SubLocation::factory()->create(['deleted_at' => now()]);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->filterTable('trashed', 'with')
->callTableAction('restore', $subLocation);
$this->assertDatabaseHas('sub_locations', [
'id' => $subLocation->id,
'deleted_at' => null,
]);
});
test('can force delete sub-location', function () {
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
$subLocation = SubLocation::factory()->create(['deleted_at' => now()]);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->filterTable('trashed', 'only')
->callTableAction('forceDelete', $subLocation);
$this->assertDatabaseMissing('sub_locations', ['id' => $subLocation->id]);
});
test('trashed filter visibility depends on developer role', function () {
$roleAdmin = Role::firstOrCreate(['name' => RoleEnum::ADMINISTRATOR->value, 'guard_name' => 'web']);
$this->user->assignRole($roleAdmin);
$this->actingAs($this->user);
Livewire::test(ManageSubLocations::class)
->assertTableFilterHidden('trashed');
$roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']);
$this->user->assignRole($roleDev);
Livewire::test(ManageSubLocations::class)
->assertTableFilterVisible('trashed');
});