Merge pull request #1516 from PhrozenByte/feature/commands

Add polls:share:* CLI commands
This commit is contained in:
René Gieling 2021-05-02 10:05:20 +02:00 коммит произвёл GitHub
Родитель 18d1341579 32923834df
Коммит c53aabf354
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 972 добавлений и 34 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -8,6 +8,7 @@ css/*.map
js/*
nbproject/
node_modules/
tests/.phpunit.result.cache
npm-debug.log
vendor
Thumbs.db

Просмотреть файл

@ -28,6 +28,10 @@
<background-jobs>
<job>OCA\Polls\Cron\NotificationCron</job>
</background-jobs>
<commands>
<command>OCA\Polls\Command\Share\Add</command>
<command>OCA\Polls\Command\Share\Remove</command>
</commands>
<repair-steps>
<pre-migration>
<step>OCA\Polls\Migration\DeleteDuplicates</step>

145
lib/Command/Share/Add.php Normal file
Просмотреть файл

@ -0,0 +1,145 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Command\Share;
use OC\Core\Command\Base;
use OCA\Polls\Db\Poll;
use OCA\Polls\Exceptions\ShareAlreadyExistsException;
use OCA\Polls\Model\Email;
use OCA\Polls\Model\Group;
use OCA\Polls\Model\User;
use OCP\AppFramework\Db\DoesNotExistException;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Add extends Base {
use TShareCommand;
protected function configure(): void {
$this
->setName('polls:share:add')
->setDescription('Invites users to a poll')
->addArgument(
'id',
InputArgument::REQUIRED,
'ID of the poll to invite users to'
)->addOption(
'user',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Invites the given users to the poll'
)->addOption(
'group',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Invites all members of the given groups to the poll'
)->addOption(
'email',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sends invitation e-mails to the given addresses to participate in the poll'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$pollId = $input->getArgument('id');
$users = $input->getOption('user');
$groups = $input->getOption('group');
$emails = $input->getOption('email');
try {
$poll = $this->pollMapper->find($pollId);
} catch (DoesNotExistException $e) {
$output->writeln('<error>Poll not found.</error>');
return 1;
}
$this->inviteUsers($poll, $users);
$this->inviteGroups($poll, $groups);
$this->inviteEmails($poll, $emails);
$output->writeln('<info>Users successfully invited to poll.</info>');
return 0;
}
/**
* @param Poll $poll
* @param string[] $userIds
*/
private function inviteUsers(Poll $poll, array $userIds): void {
foreach ($userIds as $userId) {
try {
$share = $this->shareService->add($poll->getId(), User::TYPE, $userId);
$this->shareService->sendInvitation($share->getToken());
} catch (ShareAlreadyExistsException $e) {
// silently ignore already existing shares
}
}
}
/**
* @param Poll $poll
* @param string[] $groupIds
*/
private function inviteGroups(Poll $poll, array $groupIds): void {
foreach ($groupIds as $groupId) {
try {
$share = $this->shareService->add($poll->getId(), Group::TYPE, $groupId);
$this->shareService->sendInvitation($share->getToken());
} catch (ShareAlreadyExistsException $e) {
// silently ignore already existing shares
}
}
}
/**
* @param Poll $poll
* @param string[] $emails
*/
private function inviteEmails(Poll $poll, array $emails): void {
foreach ($emails as $email) {
try {
$share = $this->shareService->add($poll->getId(), Email::TYPE, $email);
$this->shareService->sendInvitation($share->getToken());
} catch (ShareAlreadyExistsException $e) {
// silently ignore already existing shares
}
}
}
public function completeOptionValues($optionName, CompletionContext $context) {
switch ($optionName) {
case 'user':
return $this->completeUserValues($context);
case 'group':
return $this->completeGroupValues($context);
}
return parent::completeOptionValues($optionName, $context);
}
}

Просмотреть файл

@ -0,0 +1,162 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Command\Share;
use OC\Core\Command\Base;
use OCA\Polls\Db\Poll;
use OCA\Polls\Db\Share;
use OCA\Polls\Model\Contact;
use OCA\Polls\Model\Email;
use OCA\Polls\Model\GenericUser;
use OCA\Polls\Model\Group;
use OCA\Polls\Model\User;
use OCP\AppFramework\Db\DoesNotExistException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Remove extends Base {
use TShareCommand;
protected function configure(): void {
$this
->setName('polls:share:remove')
->setDescription('Remove user invitations from a poll')
->addArgument(
'id',
InputArgument::REQUIRED,
'ID of the poll to remove invitations from'
)->addOption(
'user',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Removes invitation of the given users from the poll'
)->addOption(
'group',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Removes invitations for all members of the given groups from the poll'
)->addOption(
'email',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Removes invitations for all users with the given e-mail addresses from the poll'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$pollId = $input->getArgument('id');
$users = $input->getOption('user');
$groups = $input->getOption('group');
$emails = $input->getOption('email');
try {
$poll = $this->pollMapper->find($pollId);
} catch (DoesNotExistException $e) {
$output->writeln('<error>Poll not found.</error>');
return 1;
}
$this->removeUsers($poll, $users);
$this->removeGroups($poll, $groups);
$this->removeEmails($poll, $emails);
$output->writeln('<info>Poll invitations successfully revoked.</info>');
return 0;
}
/**
* @param Poll $poll
* @param string[] $userIds
*/
private function removeUsers(Poll $poll, array $userIds): void {
foreach ($this->getUserShares($poll) as $share) {
if (in_array($share->getUserId(), $userIds, true)) {
$this->shareService->delete($share->getToken());
}
}
}
/**
* @param Poll $poll
* @param string[] $groupIds
*/
private function removeGroups(Poll $poll, array $groupIds): void {
foreach ($this->getGroupShares($poll) as $share) {
if (in_array($share->getUserId(), $groupIds, true)) {
$this->shareService->delete($share->getToken());
}
}
}
/**
* @param Poll $poll
* @param string[] $emails
*/
private function removeEmails(Poll $poll, array $emails): void {
foreach ($this->getEmailShares($poll) as $share) {
if (in_array($share->getEmailAddress(), $emails, true)) {
$this->shareService->delete($share->getToken());
}
}
}
/**
* @param Poll $poll
* @return Share[]
*/
private function getUserShares(Poll $poll): array {
$shares = $this->shareMapper->findByPoll($poll->getId());
return array_values(array_filter($shares, static function (Share $share): bool {
return ($share->getType() === User::TYPE);
}));
}
/**
* @param Poll $poll
* @return Share[]
*/
private function getGroupShares(Poll $poll): array {
$shares = $this->shareMapper->findByPoll($poll->getId());
return array_values(array_filter($shares, static function (Share $share): bool {
return ($share->getType() === Group::TYPE);
}));
}
/**
* @param Poll $poll
* @return Share[]
*/
private function getEmailShares(Poll $poll): array {
$shares = $this->shareMapper->findByPoll($poll->getId());
return array_values(array_filter($shares, static function (Share $share): bool {
if (($share->getType() === GenericUser::TYPE) && $share->getEmailAddress()) {
return true;
}
return (($share->getType() === Email::TYPE) || ($share->getType() === Contact::TYPE));
}));
}
}

Просмотреть файл

@ -0,0 +1,76 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Command\Share;
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Service\ShareService;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
trait TShareCommand {
/** @var PollMapper */
private $pollMapper;
/** @var ShareService */
private $shareService;
/** @var ShareMapper */
private $shareMapper;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
private $groupManager;
public function __construct(PollMapper $pollMapper,
ShareMapper $shareMapper,
ShareService $shareService,
IUserManager $userManager,
IGroupManager $groupManager) {
parent::__construct();
$this->pollMapper = $pollMapper;
$this->shareMapper = $shareMapper;
$this->shareService = $shareService;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
}
private function completeUserValues(CompletionContext $context): array {
return array_map(function (IUser $user) {
return $user->getUID();
}, $this->userManager->search($context->getCurrentWord()));
}
private function completeGroupValues(CompletionContext $context): array {
return array_map(function (IGroup $group) {
return $group->getGID();
}, $this->groupManager->search($context->getCurrentWord()));
}
}

Просмотреть файл

@ -32,10 +32,8 @@ use OCP\AppFramework\Http\DataResponse;
use OCA\Polls\Db\Share;
use OCA\Polls\Service\MailService;
use OCA\Polls\Service\NotificationService;
use OCA\Polls\Service\ShareService;
use OCA\Polls\Service\SystemService;
use OCA\Polls\Model\User;
use OCA\Polls\Model\Circle;
use OCA\Polls\Model\ContactGroup;
@ -51,22 +49,17 @@ class ShareController extends Controller {
/** @var SystemService */
private $systemService;
/** @var NotificationService */
private $notificationService;
public function __construct(
string $appName,
IRequest $request,
MailService $mailService,
ShareService $shareService,
SystemService $systemService,
NotificationService $notificationService
SystemService $systemService
) {
parent::__construct($appName, $request);
$this->mailService = $mailService;
$this->shareService = $shareService;
$this->systemService = $systemService;
$this->notificationService = $notificationService;
}
/**
@ -144,21 +137,9 @@ class ShareController extends Controller {
*/
public function sendInvitation($token): DataResponse {
return $this->response(function () use ($token) {
$share = $this->shareService->get($token);
if ($share->getType() === Share::TYPE_USER) {
$this->notificationService->sendInvitation($share->getPollId(), $share->getUserId());
// TODO: skip this atm, to send invitations as mail too, if user is a site user
// $sentResult = ['sentMails' => [new User($share->getuserId())]];
// $this->shareService->setInvitationSent($token);
} elseif ($share->getType() === Share::TYPE_GROUP) {
foreach ($share->getMembers() as $member) {
$this->notificationService->sendInvitation($share->getPollId(), $member->getId());
}
}
$sentResult = $this->mailService->sendInvitation($token);
return [
'share' => $share,
'sentResult' => $sentResult
'share' => $this->shareService->get($token),
'sentResult' => $this->shareService->sendInvitation($token),
];
});
}

Просмотреть файл

@ -252,10 +252,11 @@ class Acl implements JsonSerializable {
/**
* hasAdminAccess - Has user administrative rights?
* Returns true, if user is in admin group and poll has allowed admins to manage the poll
* Returns true, if user is in admin group and poll has allowed admins to manage the poll,
* or when running console commands.
*/
private function hasAdminAccess(): bool {
return ($this->getIsAdmin() && $this->poll->getAdminAccess());
return (($this->getIsAdmin() && $this->poll->getAdminAccess()) || defined('OC_CONSOLE'));
}
/**

Просмотреть файл

@ -67,6 +67,9 @@ class ShareService {
/** @var Acl */
private $acl;
/** @var NotificationService */
private $notificationService;
public function __construct(
string $AppName,
LoggerInterface $logger,
@ -76,7 +79,8 @@ class ShareService {
ShareMapper $shareMapper,
Share $share,
MailService $mailService,
Acl $acl
Acl $acl,
NotificationService $notificationService
) {
$this->appName = $AppName;
$this->logger = $logger;
@ -87,6 +91,7 @@ class ShareService {
$this->share = $share;
$this->mailService = $mailService;
$this->acl = $acl;
$this->notificationService = $notificationService;
}
/**
@ -347,4 +352,25 @@ class ShareService {
}
return $token;
}
/**
* Sent invitation mails for a share
* Additionally send notification via notifications
*/
public function sendInvitation(string $token): array {
$share = $this->get($token);
if ($share->getType() === Share::TYPE_USER) {
$this->notificationService->sendInvitation($share->getPollId(), $share->getUserId());
// TODO: skip this atm, to send invitations as mail too, if user is a site user
// $sentResult = ['sentMails' => [new User($share->getuserId())]];
// $this->shareService->setInvitationSent($token);
} elseif ($share->getType() === Share::TYPE_GROUP) {
foreach ($share->getMembers() as $member) {
$this->notificationService->sendInvitation($share->getPollId(), $member->getId());
}
}
return $this->mailService->sendInvitation($token);
}
}

Просмотреть файл

@ -21,7 +21,7 @@
*
*/
namespace OCA\Polls\Tests\Integration;
namespace OCA\Polls\Tests\Integration\App;
use OCP\AppFramework\App;
use PHPUnit\Framework\TestCase;

Просмотреть файл

@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Tests\Integration\Command\Share;
use OCA\Polls\Command\Share\Add;
use OCA\Polls\Db\Poll;
use OCA\Polls\Db\Share;
use OCA\Polls\Exceptions\ShareAlreadyExistsException;
use OCA\Polls\Model\Email;
use OCA\Polls\Model\Group;
use OCA\Polls\Model\User;
use OCP\AppFramework\Db\DoesNotExistException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Exception\RuntimeException as ConsoleRuntimeException;
use Symfony\Component\Console\Tester\CommandTester;
class AddTest extends TestCase {
use TShareCommandTest;
public function setUp(): void {
parent::setUp();
$this->setUpMocks();
}
public function testMissingArguments(): void {
$this->pollMapper
->expects($this->never())
->method('find');
$this->expectException(ConsoleRuntimeException::class);
$this->expectExceptionMessage('Not enough arguments (missing: "id").');
$command = new Add(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute([]);
}
public function testPollNotFound(): void {
$pollId = 123;
$this->pollMapper
->expects($this->once())
->method('find')
->with($pollId)
->willReturnCallback(function (int $id): Poll {
throw new DoesNotExistException('');
});
$command = new Add(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute(['id' => $pollId]);
$this->assertEquals("Poll not found.\n", $tester->getDisplay());
}
/**
* @dataProvider validProvider
*/
public function testValid(array $input, array $pollData): void {
$expectedShareCount = count($pollData['expectedShares']['user'] ?? [])
+ count($pollData['expectedShares']['group'] ?? [])
+ count($pollData['expectedShares']['email'] ?? []);
$expectedInvitationCount = count($pollData['expectedInvitations']['user'] ?? [])
+ count($pollData['expectedInvitations']['group'] ?? [])
+ count($pollData['expectedInvitations']['email'] ?? []);
$expectedInvitationShareTokens = [];
foreach ($pollData['expectedInvitations'] ?? [] as $type => $shares) {
foreach ($shares as $userId) {
$expectedInvitationShareTokens[] = $this->getShareToken($pollData['pollId'], $type, $userId);
}
}
$this->pollMapper
->expects($this->once())
->method('find')
->with($pollData['pollId'])
->willReturnCallback([$this, 'createPollMock']);
$this->shareService
->expects($this->exactly($expectedShareCount))
->method('add')
->with($pollData['pollId'], $this->logicalOr(User::TYPE, Group::TYPE, Email::TYPE), $this->anything())
->willReturnCallback(function (int $pollId, string $type, string $userId = '') use ($pollData): Share {
$userIdConstraint = $this->logicalOr(...$pollData['expectedShares'][$type] ?? []);
$userIdConstraint->evaluate($userId);
if (in_array($userId, $pollData['initialShares'][$type] ?? [])) {
throw new ShareAlreadyExistsException();
}
return $this->createShareMock($pollId, $type, $userId);
});
$this->shareService
->expects($this->exactly($expectedInvitationCount))
->method('sendInvitation')
->with($this->logicalOr(...$expectedInvitationShareTokens));
$command = new Add(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute($input);
$this->assertEquals("Users successfully invited to poll.\n", $tester->getDisplay());
}
public function validProvider(): array {
return [
[
[
'id' => 1,
],
[
'pollId' => 1,
],
],
[
[
'id' => 123,
'--user' => ['user1', 'user2'],
'--group' => ['group1'],
'--email' => ['foo@example.com', 'bar@example.com'],
],
[
'pollId' => 123,
'expectedShares' => [
'user' => ['user1', 'user2'],
'group' => ['group1'],
'email' => ['foo@example.com', 'bar@example.com'],
],
'expectedInvitations' => [
'user' => ['user1', 'user2'],
'group' => ['group1'],
'email' => ['foo@example.com', 'bar@example.com'],
],
],
],
[
[
'id' => 456,
'--user' => ['user2', 'user3', 'user4'],
'--email' => ['foo@example.com', 'bar@example.com'],
],
[
'pollId' => 456,
'initialShares' => [
'user' => ['user1', 'user2'],
'email' => ['foo@example.com'],
],
'expectedShares' => [
'user' => ['user2', 'user3', 'user4'],
'email' => ['foo@example.com', 'bar@example.com'],
],
'expectedInvitations' => [
'user' => ['user3', 'user4'],
'email' => ['bar@example.com'],
],
],
],
];
}
}

Просмотреть файл

@ -0,0 +1,208 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Tests\Integration\Command\Share;
use OCA\Polls\Command\Share\Remove;
use OCA\Polls\Db\Poll;
use OCP\AppFramework\Db\DoesNotExistException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Exception\RuntimeException as ConsoleRuntimeException;
use Symfony\Component\Console\Tester\CommandTester;
class RemoveTest extends TestCase {
use TShareCommandTest;
public function setUp(): void {
parent::setUp();
$this->setUpMocks();
}
public function testMissingArguments(): void {
$this->pollMapper
->expects($this->never())
->method('find');
$this->expectException(ConsoleRuntimeException::class);
$this->expectExceptionMessage('Not enough arguments (missing: "id").');
$command = new Remove(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute([]);
}
public function testPollNotFound(): void {
$pollId = 123;
$this->pollMapper
->expects($this->once())
->method('find')
->with($pollId)
->willReturnCallback(static function (int $id): Poll {
throw new DoesNotExistException('');
});
$command = new Remove(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute(['id' => $pollId]);
$this->assertEquals("Poll not found.\n", $tester->getDisplay());
}
/**
* @dataProvider validProvider
*/
public function testValid(array $input, array $pollData): void {
$initialShares = [];
$expectedShareCount = 0;
$expectedShareTokens = [];
foreach ($pollData['initialShares'] ?? [] as $type => $shares) {
foreach ($shares as $userId) {
$initialShares[] = $this->createShareMock($pollData['pollId'], $type, $userId);
if (in_array($userId, $pollData['expectedShares'][$type] ?? [])) {
$expectedShareTokens[] = $this->getShareToken($pollData['pollId'], $type, $userId);
$expectedShareCount++;
}
}
}
$this->pollMapper
->expects($this->once())
->method('find')
->with($pollData['pollId'])
->willReturnCallback([$this, 'createPollMock']);
$this->shareMapper
->method('findByPoll')
->with($pollData['pollId'])
->willReturn($initialShares);
$this->shareService
->expects($this->exactly($expectedShareCount))
->method('delete')
->with($this->logicalOr(...$expectedShareTokens));
$command = new Remove(
$this->pollMapper,
$this->shareMapper,
$this->shareService,
$this->userManager,
$this->groupManager
);
$tester = new CommandTester($command);
$tester->execute($input);
$this->assertEquals("Poll invitations successfully revoked.\n", $tester->getDisplay());
}
public function validProvider(): array {
return [
[
[
'id' => 1,
],
[
'pollId' => 1,
],
],
[
[
'id' => 123,
'--user' => ['user1', 'user2'],
'--group' => ['group1'],
'--email' => ['foo@example.com'],
],
[
'pollId' => 123,
'initialShares' => [
'user' => ['user1', 'user2', 'user3'],
'group' => ['group1'],
'email' => ['foo@example.com', 'bar@example.com'],
],
'expectedShares' => [
'user' => ['user1', 'user2'],
'group' => ['group1'],
'email' => ['foo@example.com'],
],
],
],
[
[
'id' => 456,
'--user' => ['user1', 'user2', 'user3', 'user4'],
'--email' => ['foo@example.com', 'bar@example.com'],
],
[
'pollId' => 456,
'initialShares' => [
'user' => ['user2', 'user3'],
'email' => ['foo@example.com', 'baz@example.com'],
'contact' => ['bar@example.com'],
],
'expectedShares' => [
'user' => ['user2', 'user3'],
'email' => ['foo@example.com'],
'contact' => ['bar@example.com'],
],
]
],
[
[
'id' => 789,
'--group' => ['group1'],
'--email' => ['foo@example.com', 'baz@example.com'],
],
[
'pollId' => 789,
'initialShares' => [
'user' => ['user1', 'user2'],
'email' => ['foo@example.com'],
'contact' => ['bar@example.com'],
'external' => ['baz@example.com'],
],
'expectedShares' => [
'email' => ['foo@example.com'],
'external' => ['baz@example.com'],
],
]
],
];
}
}

Просмотреть файл

@ -0,0 +1,129 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Polls\Tests\Integration\Command\Share;
use OCA\Polls\Db\Poll;
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Service\ShareService;
use OCP\IGroupManager;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
trait TShareCommandTest {
/** @var PollMapper|MockObject */
protected $pollMapper;
/** @var ShareMapper|MockObject */
protected $shareMapper;
/** @var ShareService|MockObject */
protected $shareService;
/** @var IUserManager|MockObject */
protected $userManager;
/** @var IGroupManager|MockObject */
protected $groupManager;
/** @var int */
protected $lastShareId = 0;
protected function setUpMocks(): void {
$this->pollMapper = $this->createMock(PollMapper::class);
$this->shareMapper = $this->createMock(ShareMapper::class);
$this->shareService = $this->createMock(ShareService::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
}
public function createPollMock(int $id): Poll {
/** @var Poll|MockObject $poll */
$poll = $this->createMock(
Poll::class,
['getId']
);
$poll->method('getId')
->willReturn($id);
return $poll;
}
public function createShareMock(int $pollId, string $type, string $userId): Share {
/** @var Share|MockObject $share */
$share = $this->createMock(
Share::class,
['getId', 'getPollId', 'getType', 'getEmailAddress', 'getToken'],
['getUserId']
);
$id = ++$this->lastShareId;
$token = $this->getShareToken($pollId, $type, $userId);
$share->method('getId')
->willReturn($id);
$share->method('getPollId')
->willReturn($pollId);
$share->method('getType')
->willReturn($type);
$share->method('getUserId')
->willReturn($userId);
$share->method('getEmailAddress')
->willReturn($userId);
$share->method('getToken')
->willReturn($token);
return $share;
}
public function getShareToken(int $pollId, string $type, string $userId): string {
return substr(md5($pollId . '_' . $type . '_' . $userId), 0, 16);
}
protected function createMock($originalClassName, array $addMethods = null, array $onlyMethods = null): MockObject {
$mockBuilder = $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->disableAutoReturnValueGeneration();
if ($addMethods !== null) {
$mockBuilder->addMethods($addMethods);
}
if ($onlyMethods !== null) {
$mockBuilder->onlyMethods($onlyMethods);
}
return $mockBuilder->getMock();
}
}

Просмотреть файл

@ -1,12 +1,12 @@
<phpunit bootstrap="bootstrap.php" colors="true">
<testsuites>
<testsuite name="integration-app">
<directory>Integration/app</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./../lib</directory>
<directory suffix=".php">../lib</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="integration">
<directory>./Integration</directory>
</testsuite>
</testsuites>
</phpunit>

Просмотреть файл

@ -1,7 +1,7 @@
<phpunit bootstrap="./bootstrap.php" colors="true">
<phpunit bootstrap="bootstrap.php" colors="true">
<filter>
<whitelist>
<directory suffix=".php">lib</directory>
<directory suffix=".php">../lib</directory>
</whitelist>
</filter>
<testsuites>