dstpabuaran.com/tests/Feature/Admin/Settings/RoleTest.php
Yoga Pangestu 49841a662c Refactor marketplace settings: remove MarketplaceFeeRule, update related database and UI components
- Deleted MarketplaceFeeRule class and its usage in settings migration.
- Removed 'is_affiliate' field from orders and related factories.
- Updated notifications table to use longText for body.
- Adjusted role permissions by removing marketplace-related permissions.
- Cleaned up admin settings page by removing marketplace settings section and related components.
- Updated tests to reflect the removal of marketplace settings and ensure other settings remain unaffected.
2026-08-14 03:20:18 +07:00

782 lines
24 KiB
PHP

<?php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
uses(RefreshDatabase::class);
beforeEach(function () {
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$modules = [
'user' => ['view', 'create', 'update', 'delete', 'toggle-active', 'reset-password'],
'category' => ['view', 'create', 'update', 'delete'],
'supplier' => ['view', 'create', 'update', 'delete'],
'customer' => ['view', 'create', 'update', 'delete'],
'cash-account' => ['view', 'deposit', 'withdrawal', 'update', 'delete'],
'expense' => ['view', 'create', 'update', 'delete'],
'employee-advance' => ['view', 'create', 'update', 'delete', 'approve', 'pay'],
'payroll-period' => ['view', 'show', 'current', 'close', 'reopen'],
'payroll' => ['pay', 'cancel'],
'payroll-adjustment' => ['create', 'delete'],
'leave-request' => ['view', 'create', 'update', 'delete', 'approve', 'reject'],
'attendance' => ['view', 'check-in', 'check-out', 'by-date'],
'settings' => ['view', 'update-system', 'update-homepage', 'update-social-media', 'update-hr'],
];
foreach ($modules as $module => $actions) {
foreach ($actions as $action) {
Permission::create(['name' => "{$module}.{$action}"]);
}
}
$allPermissions = Permission::all()->pluck('name')->toArray();
$developer = Role::create(['name' => 'Developer']);
$developer->syncPermissions($allPermissions);
});
function createUserWithRole(string $roleName = 'Developer'): User
{
$user = User::factory()->create();
$user->assignRole($roleName);
return $user;
}
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.settings.roles.index'));
$response->assertRedirect(route('login'));
});
test('authenticated users without permission cannot visit the role index page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.settings.roles.index'));
$response->assertForbidden();
});
test('authenticated users with permission can visit the role index page', function () {
$this->actingAs(createUserWithRole());
$response = $this->get(route('admin.settings.roles.index'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/
test('role index page displays roles', function () {
$this->actingAs(createUserWithRole());
Role::create(['name' => 'Manager']);
Role::create(['name' => 'Staff']);
$response = $this->get(route('admin.settings.roles.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles.data', 3)
->where('roles.total', 3)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
test('index page works with zero additional roles', function () {
$this->actingAs(createUserWithRole());
$response = $this->get(route('admin.settings.roles.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles.data', 1)
->where('roles.total', 1)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
test('index page displays correct role count after delete', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'To Delete']);
$this->delete(route('admin.settings.roles.destroy', $role));
$response = $this->get(route('admin.settings.roles.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles.data', 1)
->where('roles.total', 1)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
/*
|--------------------------------------------------------------------------
| CREATE / STORE
|--------------------------------------------------------------------------
*/
test('authenticated users without permission cannot visit the create page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.settings.roles.create'));
$response->assertForbidden();
});
test('authenticated users with permission can visit the create page', function () {
$this->actingAs(createUserWithRole());
$response = $this->get(route('admin.settings.roles.create'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/create')
->has('permissions')
);
});
test('role can be created', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Manager',
'permissions' => ['user.view', 'user.create'],
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.settings.roles.index'));
$this->assertDatabaseHas('roles', ['name' => 'Manager']);
});
test('role name is required', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => '',
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role name must be string', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 12345,
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role name must not exceed 100 characters', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => str_repeat('a', 101),
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role name exactly 100 characters passes validation', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => str_repeat('a', 100),
'permissions' => ['user.view'],
]);
$response->assertSessionHasNoErrors();
});
test('role name must be unique', function () {
$this->actingAs(createUserWithRole());
Role::create(['name' => 'Existing Role']);
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Existing Role',
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role name uniqueness is case sensitive', function () {
$this->actingAs(createUserWithRole());
Role::create(['name' => 'Manager']);
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Manager',
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role name with special characters is accepted', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Admin & Staff!',
'permissions' => ['user.view'],
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('roles', ['name' => 'Admin & Staff!']);
});
test('permissions are required', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'New Role',
]);
$response->assertSessionHasErrors('permissions');
});
test('permissions must be an array', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'New Role',
'permissions' => 'user.view',
]);
$response->assertSessionHasErrors('permissions');
});
test('permissions must exist in database', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'New Role',
'permissions' => ['nonexistent.permission'],
]);
$response->assertSessionHasErrors('permissions.0');
});
test('role can be created with multiple permissions', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Multi Permission Role',
'permissions' => ['user.view', 'user.create', 'category.view'],
]);
$response->assertSessionHasNoErrors();
$role = Role::where('name', 'Multi Permission Role')->first();
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.view', 'user.create', 'user.view']);
});
test('role can be created with empty permissions array', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'No Permission Role',
'permissions' => [],
]);
$response->assertSessionHasNoErrors();
$role = Role::where('name', 'No Permission Role')->first();
expect($role->permissions->count())->toBe(0);
});
test('store flashes success toast via Inertia', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Toast Role',
'permissions' => ['user.view'],
]);
$response->assertRedirect();
});
test('store creates new role in database', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), [
'name' => 'Database Check',
'permissions' => ['user.view'],
]);
$this->assertDatabaseCount('roles', 2);
$this->assertDatabaseHas('roles', ['name' => 'Database Check']);
});
test('multiple roles can be created sequentially', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), ['name' => 'Role 1', 'permissions' => ['user.view']]);
$this->post(route('admin.settings.roles.store'), ['name' => 'Role 2', 'permissions' => ['user.view']]);
$this->post(route('admin.settings.roles.store'), ['name' => 'Role 3', 'permissions' => ['user.view']]);
$this->assertDatabaseCount('roles', 4);
$this->assertDatabaseHas('roles', ['name' => 'Role 1']);
$this->assertDatabaseHas('roles', ['name' => 'Role 2']);
$this->assertDatabaseHas('roles', ['name' => 'Role 3']);
});
/*
|--------------------------------------------------------------------------
| EDIT / UPDATE
|--------------------------------------------------------------------------
*/
test('authenticated users without permission cannot visit the edit page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$role = Role::create(['name' => 'To Edit']);
$response = $this->get(route('admin.settings.roles.edit', $role));
$response->assertForbidden();
});
test('authenticated users with permission can visit the edit page', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'To Edit']);
$response = $this->get(route('admin.settings.roles.edit', $role));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/edit')
->has('role')
->has('permissions')
);
});
test('role can be updated', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Old Name']);
$role->syncPermissions(['user.view']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'New Name',
'permissions' => ['user.view', 'user.create'],
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.settings.roles.index'));
$role->refresh();
expect($role->name)->toBe('New Name');
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['user.create', 'user.view']);
});
test('role name can be updated to itself', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Same Name']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'Same Name',
'permissions' => ['user.view'],
]);
$response->assertSessionHasNoErrors();
});
test('role update name must be unique excluding itself', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'First']);
Role::create(['name' => 'Second']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'Second',
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role update name is required', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Existing']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => '',
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role update name must not exceed 100 characters', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Existing']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => str_repeat('a', 101),
'permissions' => ['user.view'],
]);
$response->assertSessionHasErrors('name');
});
test('role permissions can be updated', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Update Perm']);
$role->syncPermissions(['user.view']);
$this->put(route('admin.settings.roles.update', $role), [
'name' => 'Update Perm',
'permissions' => ['category.view', 'category.create'],
]);
$role->refresh();
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.create', 'category.view']);
});
test('role can be updated multiple times', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Version 1']);
$this->put(route('admin.settings.roles.update', $role), ['name' => 'Version 2', 'permissions' => ['user.view']]);
$role->refresh();
expect($role->name)->toBe('Version 2');
$this->put(route('admin.settings.roles.update', $role), ['name' => 'Version 3', 'permissions' => ['user.view']]);
$role->refresh();
expect($role->name)->toBe('Version 3');
});
test('update flashes success toast via Inertia', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Toast']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'Updated Toast',
'permissions' => ['user.view'],
]);
$response->assertRedirect();
});
test('updating non-existent role returns 404', function () {
$this->actingAs(createUserWithRole());
$response = $this->put(route('admin.settings.roles.update', 999999), [
'name' => 'Ghost Role',
'permissions' => ['user.view'],
]);
$response->assertStatus(404);
});
/*
|--------------------------------------------------------------------------
| DELETE / DESTROY
|--------------------------------------------------------------------------
*/
test('authenticated users without permission cannot delete roles', function () {
$user = User::factory()->create();
$this->actingAs($user);
$role = Role::create(['name' => 'To Delete']);
$response = $this->delete(route('admin.settings.roles.destroy', $role));
$response->assertForbidden();
$this->assertDatabaseHas('roles', ['id' => $role->id]);
});
test('role can be deleted', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'To Delete']);
$response = $this->delete(route('admin.settings.roles.destroy', $role));
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.settings.roles.index'));
$this->assertDatabaseMissing('roles', ['id' => $role->id]);
});
test('role permissions are removed when role is deleted', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'With Perms']);
$role->syncPermissions(['user.view', 'user.create']);
$this->delete(route('admin.settings.roles.destroy', $role));
$this->assertDatabaseMissing('roles', ['id' => $role->id]);
$this->assertDatabaseMissing('role_has_permissions', ['role_id' => $role->id]);
});
test('delete flashes success toast via Inertia', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Toast Delete']);
$response = $this->delete(route('admin.settings.roles.destroy', $role));
$response->assertRedirect();
});
test('deleting non-existent role returns 404', function () {
$this->actingAs(createUserWithRole());
$response = $this->delete(route('admin.settings.roles.destroy', 999999));
$response->assertStatus(404);
});
/*
|--------------------------------------------------------------------------
| AUTHORIZATION - GUEST CANNOT PERFORM ACTIONS
|--------------------------------------------------------------------------
*/
test('guest cannot create role', function () {
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Unauthorized',
'permissions' => ['user.view'],
]);
$response->assertRedirect(route('login'));
$this->assertDatabaseMissing('roles', ['name' => 'Unauthorized']);
});
test('guest cannot update role', function () {
$role = Role::create(['name' => 'Existing']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'Unauthorized Update',
'permissions' => ['user.view'],
]);
$response->assertRedirect(route('login'));
});
test('guest cannot delete role', function () {
$role = Role::create(['name' => 'Existing']);
$response = $this->delete(route('admin.settings.roles.destroy', $role));
$response->assertRedirect(route('login'));
$this->assertDatabaseHas('roles', ['id' => $role->id]);
});
/*
|--------------------------------------------------------------------------
| AUTHORIZATION - USER WITHOUT ROLE CANNOT PERFORM ACTIONS
|--------------------------------------------------------------------------
*/
test('user without developer role cannot create role', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.settings.roles.store'), [
'name' => 'Unauthorized',
'permissions' => ['user.view'],
]);
$response->assertForbidden();
$this->assertDatabaseMissing('roles', ['name' => 'Unauthorized']);
});
test('user without developer role cannot update role', function () {
$user = User::factory()->create();
$this->actingAs($user);
$role = Role::create(['name' => 'Existing']);
$response = $this->put(route('admin.settings.roles.update', $role), [
'name' => 'Unauthorized Update',
'permissions' => ['user.view'],
]);
$response->assertForbidden();
});
test('user without developer role cannot delete role', function () {
$user = User::factory()->create();
$this->actingAs($user);
$role = Role::create(['name' => 'Existing']);
$response = $this->delete(route('admin.settings.roles.destroy', $role));
$response->assertForbidden();
$this->assertDatabaseHas('roles', ['id' => $role->id]);
});
/*
|--------------------------------------------------------------------------
| DATA INTEGRITY
|--------------------------------------------------------------------------
*/
test('created role has correct timestamps', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), [
'name' => 'Timestamp Role',
'permissions' => ['user.view'],
]);
$role = Role::where('name', 'Timestamp Role')->first();
expect($role->created_at)->not->toBeNull();
expect($role->updated_at)->not->toBeNull();
});
/*
|--------------------------------------------------------------------------
| PERMISSION ASSIGNMENT
|--------------------------------------------------------------------------
*/
test('role permissions are correctly synced', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Sync Test']);
$role->syncPermissions(['user.view', 'user.create', 'category.view']);
$role->refresh();
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.view', 'user.create', 'user.view']);
});
test('role permissions can be replaced', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Replace Test']);
$role->syncPermissions(['user.view']);
$role->syncPermissions(['category.view', 'category.create']);
$role->refresh();
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.create', 'category.view']);
});
test('role can have all permissions', function () {
$this->actingAs(createUserWithRole());
$allPermissions = Permission::pluck('name')->toArray();
$role = Role::create(['name' => 'Super Admin']);
$role->syncPermissions($allPermissions);
$role->refresh();
expect($role->permissions->count())->toBe(count($allPermissions));
});
/*
|--------------------------------------------------------------------------
| REALISTIC USER SCENARIOS
|--------------------------------------------------------------------------
*/
test('user creates role then immediately edits it', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), [
'name' => 'Draft Role',
'permissions' => ['user.view'],
]);
$role = Role::where('name', 'Draft Role')->first();
$this->put(route('admin.settings.roles.update', $role), [
'name' => 'Final Role',
'permissions' => ['user.view', 'user.create'],
]);
$role->refresh();
expect($role->name)->toBe('Final Role');
expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['user.create', 'user.view']);
});
test('user creates multiple roles and deletes one', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), ['name' => 'Keep Role', 'permissions' => ['user.view']]);
$this->post(route('admin.settings.roles.store'), ['name' => 'Delete Role', 'permissions' => ['user.view']]);
$this->post(route('admin.settings.roles.store'), ['name' => 'Also Keep Role', 'permissions' => ['user.view']]);
$toDelete = Role::where('name', 'Delete Role')->first();
$this->delete(route('admin.settings.roles.destroy', $toDelete));
$response = $this->get(route('admin.settings.roles.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles.data', 3)
->where('roles.total', 3)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
test('user tries to create role without submitting any data', function () {
$this->actingAs(createUserWithRole());
$response = $this->post(route('admin.settings.roles.store'), []);
$response->assertSessionHasErrors(['name', 'permissions']);
});
test('user tries to update role without submitting any data', function () {
$this->actingAs(createUserWithRole());
$role = Role::create(['name' => 'Existing']);
$response = $this->put(route('admin.settings.roles.update', $role), []);
$response->assertSessionHasErrors(['name', 'permissions']);
});
test('user rapidly submits same role creation twice', function () {
$this->actingAs(createUserWithRole());
$this->post(route('admin.settings.roles.store'), ['name' => 'Rapid Submit', 'permissions' => ['user.view']]);
$response = $this->post(route('admin.settings.roles.store'), ['name' => 'Rapid Submit', 'permissions' => ['user.view']]);
$response->assertSessionHasErrors('name');
$this->assertDatabaseCount('roles', 2);
});