CaiWuXiTong/tests/Feature/SystemSettings/UserAccessGroupsTest.php

180 lines
11 KiB
PHP

<?php
namespace Tests\Feature\SystemSettings;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use App\Models\Auth\UserAccessGroup;
use App\Utilities\SidebarPermissions;
use Tests\Feature\FeatureTestCase;
class UserAccessGroupsTest extends FeatureTestCase
{
public function test_create_group_and_add_members_without_granting_administrator_identity(): void
{
$member = $this->member();
$this->loginAs()->post(route('system-settings.groups.store'), ['name' => '核算组', 'members' => [$member->id]])
->assertRedirect(route('system-settings.groups.index'));
$group = UserAccessGroup::where('name', '核算组')->sole();
$this->assertSame([], $group->page_permissions);
$this->assertSame([], SidebarPermissions::allowedKeys($member));
$this->assertSame(['user'], $member->roles()->pluck('name')->all());
$this->get(route('system-settings.users.index'))->assertOk()->assertSeeText('核算组');
$this->get(route('system-settings.groups.index'))->assertOk()->assertSeeText('未开放页面');
}
public function test_later_user_can_be_added_to_existing_groups_from_user_management(): void
{
$first = $this->member();
$group = $this->group('现有核算组', $first, ['store-management/basic-information']);
$group->users()->detach($first->id);
$later = $this->member();
$this->loginAs()->get(route('system-settings.users.index'))->assertOk()
->assertSee(route('system-settings.groups.membership.edit', $later->id), false)
->assertSeeText('管理分组');
$this->get(route('system-settings.groups.membership.edit', $later->id))->assertOk()
->assertSeeText('现有核算组')->assertSeeText($later->name);
$this->patch(route('system-settings.groups.membership.update', $later->id), ['groups' => [$group->id]])
->assertRedirect(route('system-settings.users.index'));
$this->assertTrue($group->users()->whereKey($later->id)->exists());
$this->assertSame(['store-management/basic-information'], SidebarPermissions::allowedKeys($later));
}
public function test_user_membership_editor_is_company_scoped_and_super_admin_only(): void
{
$member = $this->member();
$group = $this->group('本公司分组', $member);
$admin = $this->member('admin');
$this->withExceptionHandling()->loginAs($admin)
->get(route('system-settings.groups.membership.edit', $member->id))->assertForbidden();
$this->loginAs()->withHeaders(['X-Requested-With' => 'XMLHttpRequest'])
->get(route('system-settings.groups.membership.edit', $this->user->id))->assertNotFound();
$this->patchJson(route('system-settings.groups.membership.update', $member->id), ['groups' => [$group->id + 9999]])
->assertUnprocessable();
$this->assertSame([$group->id], UserAccessGroup::forUser($member)->pluck('id')->all());
}
public function test_multiple_group_permissions_are_unioned_and_enforced_on_real_routes(): void
{
$member = $this->member();
$a = $this->group('核算', $member, ['store-management/basic-information']);
$b = $this->group('复核', $member, ['trademark-management/information']);
$this->assertEqualsCanonicalizing(['store-management/basic-information', 'trademark-management/information'], SidebarPermissions::allowedKeys($member));
$this->withExceptionHandling()->loginAs($member)->get(route('business.pages.show', ['section' => 'company-management', 'page' => 'basic-information']))->assertForbidden();
$this->loginAs()->patch(route('system-settings.sidebar.update'), ['subject' => 'group_'.$b->id, 'permissions' => []])
->assertRedirect(route('system-settings.sidebar.edit', ['subject' => 'group_'.$b->id]));
$this->assertSame(['store-management/basic-information'], SidebarPermissions::allowedKeys($member));
$this->loginAs($member)->get(route('dashboard'))->assertRedirectContains('/workspace/store-management/basic-information');
}
public function test_user_override_can_be_empty_and_can_restore_group_inheritance(): void
{
$member = $this->member();
$this->group('共享核算组', $member, ['store-management/basic-information']);
$this->loginAs()->patch(route('system-settings.sidebar.update'), ['subject' => 'user_'.$member->id, 'permissions' => []]);
$this->assertSame([], SidebarPermissions::allowedKeys($member));
$this->get(route('system-settings.sidebar.edit', ['subject' => 'user_'.$member->id]))->assertOk()->assertSeeText('恢复继承分组权限');
$this->patch(route('system-settings.sidebar.update'), ['subject' => 'user_'.$member->id, 'mode' => 'inherit'])
->assertRedirect(route('system-settings.sidebar.edit', ['subject' => 'user_'.$member->id]));
$this->assertSame(['store-management/basic-information'], SidebarPermissions::allowedKeys($member));
$this->get(route('system-settings.sidebar.edit', ['subject' => 'user_'.$member->id]))->assertOk()->assertSeeText('正在继承分组:共享核算组');
}
public function test_group_configuration_uses_the_existing_permissions_editor(): void
{
$member = $this->member();
$group = $this->group('出纳组', $member);
$this->loginAs()->get(route('system-settings.sidebar.edit', ['subject' => 'group_'.$group->id]))->assertOk()->assertSeeText('分组:出纳组');
$this->patch(route('system-settings.sidebar.update'), ['subject' => 'group_'.$group->id, 'permissions' => ['fund-management']]);
$this->assertSame(['fund-management'], $group->fresh()->page_permissions);
$this->assertSame(['fund-management'], SidebarPermissions::allowedKeys($member));
$this->withExceptionHandling()->patchJson(route('system-settings.sidebar.update'), ['subject' => 'group_'.$group->id, 'permissions' => ['not-a-page']])->assertUnprocessable();
}
public function test_group_page_access_does_not_grant_store_password_access(): void
{
$member = $this->member();
$this->group('店铺资料组', $member, ['store-management']);
$this->mock(\App\Services\StoreAccountPasswordClient::class)->shouldNotReceive('reveal');
$this->withExceptionHandling()->loginAs($member)->postJson(route('business.store-account-passwords.reveal', ['account' => 'fixture-account']), ['purpose' => 'view'])->assertForbidden();
}
public function test_group_membership_and_names_can_change_without_changing_permissions(): void
{
$member = $this->member();
$other = $this->member();
$group = $this->group('旧名称', $member, ['store-management']);
$this->loginAs()->put(route('system-settings.groups.update', $group->id), ['name' => '新名称', 'members' => [$other->id]])
->assertRedirect(route('system-settings.groups.index'));
$this->assertSame(['store-management'], SidebarPermissions::allowedKeys($other));
$this->assertNull(SidebarPermissions::allowedKeys($member));
$this->assertSame(['store-management'], $group->fresh()->page_permissions);
$this->assertSame('新名称', $group->fresh()->name);
}
public function test_delete_requires_an_empty_group(): void
{
$member = $this->member();
$group = $this->group('核算组', $member);
$this->loginAs()->delete(route('system-settings.groups.destroy', $group->id))->assertSessionHasErrors('group');
$this->assertNotNull($group->fresh());
$this->put(route('system-settings.groups.update', $group->id), ['name' => '核算组']);
$this->delete(route('system-settings.groups.destroy', $group->id))->assertRedirect(route('system-settings.groups.index'));
$this->assertNull($group->fresh());
}
public function test_only_super_admins_can_change_groups_or_group_permissions(): void
{
$member = $this->member('admin');
$group = $this->group('管理员业务组', $member);
$this->withExceptionHandling()->loginAs($member)->get(route('system-settings.groups.index'))->assertForbidden();
$this->post(route('system-settings.groups.store'), ['name' => '不允许'])->assertForbidden();
$this->put(route('system-settings.groups.update', $group->id), ['name' => '不允许'])->assertForbidden();
$this->delete(route('system-settings.groups.destroy', $group->id))->assertForbidden();
$this->patchJson(route('system-settings.sidebar.update'), ['subject' => 'group_'.$group->id, 'permissions' => ['store-management']])->assertUnprocessable();
}
public function test_group_and_member_ids_cannot_cross_company_or_grant_super_admin_membership(): void
{
$member = $this->member();
$member->companies()->detach();
$this->withExceptionHandling()->loginAs()->postJson(route('system-settings.groups.store'), ['name' => '跨公司', 'members' => [$member->id]])->assertUnprocessable();
$this->postJson(route('system-settings.groups.store'), ['name' => '主管理员组', 'members' => [$this->user->id]])->assertUnprocessable();
$group = $this->group('本公司', $this->member());
$this->withHeaders(['X-Requested-With' => 'XMLHttpRequest'])->getJson(route('system-settings.groups.edit', $group->id + 9999))->assertNotFound();
$otherCompany = $this->company->replicate();
$otherCompany->domain = 'group-isolation.example.test';
$otherCompany->save();
$foreignGroup = new UserAccessGroup(['name' => '其他公司分组', 'page_permissions' => ['store-management']]);
$foreignGroup->company_id = $otherCompany->id;
$foreignGroup->save();
$this->getJson(route('system-settings.groups.edit', $foreignGroup->id))->assertNotFound();
$this->patchJson(route('system-settings.sidebar.update'), ['subject' => 'group_'.$foreignGroup->id, 'permissions' => []])->assertUnprocessable();
$this->postJson(route('system-settings.groups.store'), ['name' => '本公司'])->assertUnprocessable();
$this->assertNull(SidebarPermissions::allowedKeys($this->user));
}
private function member(string $role = 'user'): User
{
$name = 'member-'.User::count();
$user = User::create(['name' => $name, 'email' => $name.'@example.test', 'password' => 'fixture-only-password', 'enabled' => true, 'locale' => 'zh-CN', 'landing_page' => 'dashboard']);
$user->roles()->attach(Role::where('name', $role)->sole()->id);
$user->permissions()->attach(Permission::where('name', 'read-admin-panel')->sole()->id);
$user->companies()->attach($this->company->id);
return $user;
}
private function group(string $name, User $member, array $permissions = []): UserAccessGroup
{
$group = new UserAccessGroup(['name' => $name, 'page_permissions' => $permissions]);
$group->company_id = $this->company->id;
$group->save();
$group->users()->attach($member->id);
return $group;
}
}