createPermissionService(false, ['group-a', 'group-b'], []); $bot = $this->createBot('owner', 'groups', ['group-a', 'group-b']); $this->assertTrue($service->canApproveBot('reviewer', $bot)); $otherScopedBot = $this->createBot('owner', 'groups', ['group-a', 'group-c']); $this->assertFalse($service->canApproveBot('reviewer', $otherScopedBot)); } public function testCanApproveBotAllowsOwnerWhenTheyCanManageTargetScope(): void { $service = $this->createScopedPermissionService( static fn (string $userId): bool => $userId === 'owner', static fn (string $userId): array => $userId === 'owner' ? ['group-a'] : [], static fn (string $userId): array => [] ); $bot = $this->createBot('owner', 'groups', ['group-a']); $this->assertTrue($service->canApproveBot('owner', $bot)); $this->assertFalse($service->canApproveBot('reviewer', $bot)); } public function testCanEditBotIsBoundToConcreteBotScope(): void { $service = $this->createPermissionService(false, ['group-a'], []); $bot = $this->createBot('owner', 'groups', ['group-b']); $this->assertTrue($service->canEditBot('owner', $bot)); $this->assertFalse($service->canEditBot('reviewer', $bot)); } public function testCanApproveBotUsesPendingTargetScopeForVersionedChanges(): void { $service = $this->createPermissionService(false, ['group-a'], []); $bot = $this->createBot('owner', 'groups', ['group-a']); $bot->setApprovalStatus('pending'); $bot->setPendingChangesArray([ 'visibility' => 'groups', 'allowed_groups' => json_encode(['group-b']), 'allowed_teams' => json_encode([]), ]); $this->assertFalse($service->canApproveBot('reviewer', $bot)); } public function testCanPublishBotToScopeRequiresAllTeamAssignments(): void { $service = $this->createPermissionService(false, [], ['team-1', 'team-2']); $this->assertTrue($service->canPublishBotToScope('reviewer', 'teams', null, ['team-1', 'team-2'])); $this->assertFalse($service->canPublishBotToScope('reviewer', 'teams', null, ['team-1', 'team-3'])); } public function testCanApproveTeamBotRequiresConcreteTeamAdminEvenForGlobalAdmin(): void { $teamBot = $this->createBot('owner', 'teams', [], ['team-1']); $globalAdminOnly = $this->createPermissionService(true, [], []); $this->assertFalse($globalAdminOnly->canApproveBot('admin', $teamBot)); $this->assertTrue($globalAdminOnly->canPublishBotToScope('admin', 'teams', null, ['team-1'])); $teamAdmin = $this->createPermissionService(true, [], ['team-1']); $this->assertTrue($teamAdmin->canApproveBot('admin', $teamBot)); } public function testCanCreateBotsAllowsAdminsAndConfiguredGroupMembers(): void { $settings = new Settings(); $settings->setBotCreationRestricted(true); $settings->setBotCreationGroup('bot-creators'); $member = $this->createMock(IUser::class); $otherUser = $this->createMock(IUser::class); $groupManager = $this->createMock(IGroupManager::class); $groupManager->method('isAdmin') ->willReturnCallback(static fn (string $userId): bool => $userId === 'admin'); $groupManager->method('getUserGroupIds') ->willReturnCallback(static fn (IUser $user): array => $user === $member ? ['bot-creators'] : []); $userManager = $this->createMock(IUserManager::class); $userManager->method('get') ->willReturnCallback(static function (string $userId) use ($member, $otherUser): ?IUser { return match ($userId) { 'member' => $member, 'other' => $otherUser, default => null, }; }); $settingsService = $this->createMock(SettingsService::class); $settingsService->method('getSettings')->willReturn($settings); $service = new PermissionService( $groupManager, $userManager, $this->createMock(IAppManager::class), $settingsService, $this->createMock(LoggerInterface::class) ); $this->assertTrue($service->canCreateBots('admin')); $this->assertTrue($service->canCreateBots('member')); $this->assertFalse($service->canCreateBots('other')); } private function createPermissionService(bool $isAdmin, array $adminGroups, array $adminTeams): PermissionService { return $this->createScopedPermissionService( static fn (string $userId): bool => $isAdmin, static fn (string $userId): array => $adminGroups, static fn (string $userId): array => $adminTeams ); } private function createScopedPermissionService( callable $isAdminResolver, callable $adminGroupsResolver, callable $adminTeamsResolver ): PermissionService { $settingsService = $this->createMock(SettingsService::class); $settingsService->method('getSettings')->willReturn(new Settings()); $service = $this->getMockBuilder(PermissionService::class) ->setConstructorArgs([ $this->createMock(IGroupManager::class), $this->createMock(IUserManager::class), $this->createMock(IAppManager::class), $settingsService, $this->createMock(LoggerInterface::class), ]) ->onlyMethods(['isAdmin', 'getAdminGroups', 'getAdminTeams']) ->getMock(); $service->method('isAdmin')->willReturnCallback($isAdminResolver); $service->method('getAdminGroups')->willReturnCallback($adminGroupsResolver); $service->method('getAdminTeams')->willReturnCallback($adminTeamsResolver); return $service; } /** * @param array $groups * @param array $teams */ private function createBot(string $ownerId, string $visibility, array $groups = [], array $teams = []): Bot { $bot = new Bot(); $bot->setUserId($ownerId); $bot->setVisibility($visibility); $bot->setIsPublic($visibility === 'global'); $bot->setAllowedGroups(json_encode($groups) ?: '[]'); $bot->setAllowedTeams(json_encode($teams) ?: '[]'); return $bot; } }