diff options
Diffstat (limited to 'tests/Core/Command')
48 files changed, 6811 insertions, 0 deletions
diff --git a/tests/Core/Command/Apps/AppsDisableTest.php b/tests/Core/Command/Apps/AppsDisableTest.php new file mode 100644 index 00000000000..117af958054 --- /dev/null +++ b/tests/Core/Command/Apps/AppsDisableTest.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\Config; + +use OC\Core\Command\App\Disable; +use OCP\App\IAppManager; +use OCP\Server; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +/** + * Class AppsDisableTest + * + * @group DB + */ +class AppsDisableTest extends TestCase { + /** @var CommandTester */ + private $commandTester; + + protected function setUp(): void { + parent::setUp(); + + $command = new Disable( + Server::get(IAppManager::class) + ); + + $this->commandTester = new CommandTester($command); + + Server::get(IAppManager::class)->enableApp('admin_audit'); + Server::get(IAppManager::class)->enableApp('comments'); + } + + /** + * @param $appId + * @param $groups + * @param $statusCode + * @param $pattern + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataCommandInput')] + public function testCommandInput($appId, $statusCode, $pattern): void { + $input = ['app-id' => $appId]; + + $this->commandTester->execute($input); + + $this->assertMatchesRegularExpression('/' . $pattern . '/', $this->commandTester->getDisplay()); + $this->assertSame($statusCode, $this->commandTester->getStatusCode()); + } + + public static function dataCommandInput(): array { + return [ + [['admin_audit'], 0, 'admin_audit ([\d\.]*) disabled'], + [['comments'], 0, 'comments ([\d\.]*) disabled'], + [['invalid_app'], 0, 'No such app enabled: invalid_app'], + + [['admin_audit', 'comments'], 0, "admin_audit ([\d\.]*) disabled\ncomments ([\d\.]*) disabled"], + [['admin_audit', 'comments', 'invalid_app'], 0, "admin_audit ([\d\.]*) disabled\ncomments ([\d\.]*) disabled\nNo such app enabled: invalid_app"], + + [['files'], 2, "files can't be disabled"], + [['provisioning_api'], 2, "provisioning_api can't be disabled"], + + [['files', 'admin_audit'], 2, "files can't be disabled.\nadmin_audit ([\d\.]*) disabled"], + [['provisioning_api', 'comments'], 2, "provisioning_api can't be disabled.\ncomments ([\d\.]*) disabled"], + ]; + } +} diff --git a/tests/Core/Command/Apps/AppsEnableTest.php b/tests/Core/Command/Apps/AppsEnableTest.php new file mode 100644 index 00000000000..604c670ae15 --- /dev/null +++ b/tests/Core/Command/Apps/AppsEnableTest.php @@ -0,0 +1,84 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\Config; + +use OC\Core\Command\App\Enable; +use OC\Installer; +use OCP\App\IAppManager; +use OCP\IGroupManager; +use OCP\Server; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +/** + * Class AppsEnableTest + * + * @group DB + */ +class AppsEnableTest extends TestCase { + /** @var CommandTester */ + private $commandTester; + + protected function setUp(): void { + parent::setUp(); + + $command = new Enable( + Server::get(IAppManager::class), + Server::get(IGroupManager::class), + Server::get(Installer::class), + ); + + $this->commandTester = new CommandTester($command); + + Server::get(IAppManager::class)->disableApp('admin_audit'); + Server::get(IAppManager::class)->disableApp('comments'); + } + + /** + * @param $appId + * @param $groups + * @param $statusCode + * @param $pattern + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataCommandInput')] + public function testCommandInput($appId, $groups, $statusCode, $pattern): void { + $input = ['app-id' => $appId]; + + if (is_array($groups)) { + $input['--groups'] = $groups; + } + + $this->commandTester->execute($input); + + $this->assertMatchesRegularExpression('/' . $pattern . '/', $this->commandTester->getDisplay()); + $this->assertSame($statusCode, $this->commandTester->getStatusCode()); + } + + public static function dataCommandInput(): array { + return [ + [['admin_audit'], null, 0, 'admin_audit ([\d\.]*) enabled'], + [['comments'], null, 0, 'comments ([\d\.]*) enabled'], + [['comments', 'comments'], null, 0, "comments ([\d\.]*) enabled\ncomments already enabled"], + [['invalid_app'], null, 1, 'Could not download app invalid_app'], + + [['admin_audit', 'comments'], null, 0, "admin_audit ([\d\.]*) enabled\ncomments ([\d\.]*) enabled"], + [['admin_audit', 'comments', 'invalid_app'], null, 1, "admin_audit ([\d\.]*) enabled\ncomments ([\d\.]*) enabled\nCould not download app invalid_app"], + + [['admin_audit'], ['admin'], 1, "admin_audit can't be enabled for groups"], + [['comments'], ['admin'], 1, "comments can't be enabled for groups"], + + [['updatenotification'], ['admin'], 0, 'updatenotification ([\d\.]*) enabled for groups: admin'], + [['updatenotification', 'dashboard'], ['admin'], 0, "updatenotification ([\d\.]*) enabled for groups: admin\ndashboard ([\d\.]*) enabled for groups: admin"], + + [['updatenotification'], ['admin', 'invalid_group'], 0, 'updatenotification ([\d\.]*) enabled for groups: admin'], + [['updatenotification', 'dashboard'], ['admin', 'invalid_group'], 0, "updatenotification ([\d\.]*) enabled for groups: admin\ndashboard ([\d\.]*) enabled for groups: admin"], + [['updatenotification', 'dashboard', 'invalid_app'], ['admin', 'invalid_group'], 1, "updatenotification ([\d\.]*) enabled for groups: admin\ndashboard ([\d\.]*) enabled for groups: admin\nCould not download app invalid_app"], + ]; + } +} diff --git a/tests/Core/Command/Config/App/DeleteConfigTest.php b/tests/Core/Command/Config/App/DeleteConfigTest.php new file mode 100644 index 00000000000..9e43f389214 --- /dev/null +++ b/tests/Core/Command/Config/App/DeleteConfigTest.php @@ -0,0 +1,99 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\App; + +use OC\Config\ConfigManager; +use OC\Core\Command\Config\App\DeleteConfig; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteConfigTest extends TestCase { + protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; + protected InputInterface&MockObject $consoleInput; + protected OutputInterface&MockObject $consoleOutput; + protected Command $command; + + protected function setUp(): void { + parent::setUp(); + + $this->appConfig = $this->createMock(IAppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new DeleteConfig($this->appConfig, $this->configManager); + } + + + public static function dataDelete(): array { + return [ + [ + 'name', + true, + true, + 0, + 'info', + ], + [ + 'name', + true, + false, + 0, + 'info', + ], + [ + 'name', + false, + false, + 0, + 'info', + ], + [ + 'name', + false, + true, + 1, + 'error', + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataDelete')] + public function testDelete(string $configName, bool $configExists, bool $checkIfExists, int $expectedReturn, string $expectedMessage): void { + $this->appConfig->expects(($checkIfExists) ? $this->once() : $this->never()) + ->method('getKeys') + ->with('app-name') + ->willReturn($configExists ? [$configName] : []); + + $this->appConfig->expects(($expectedReturn === 0) ? $this->once() : $this->never()) + ->method('deleteKey') + ->with('app-name', $configName); + + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['app', 'app-name'], + ['name', $configName], + ]); + $this->consoleInput->method('hasParameterOption') + ->with('--error-if-not-exists') + ->willReturn($checkIfExists); + + $this->consoleOutput->method('writeln') + ->with($this->stringContains($expectedMessage)); + + $this->assertSame($expectedReturn, self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } +} diff --git a/tests/Core/Command/Config/App/GetConfigTest.php b/tests/Core/Command/Config/App/GetConfigTest.php new file mode 100644 index 00000000000..13392cddf55 --- /dev/null +++ b/tests/Core/Command/Config/App/GetConfigTest.php @@ -0,0 +1,138 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\App; + +use OC\Config\ConfigManager; +use OC\Core\Command\Config\App\GetConfig; +use OCP\Exceptions\AppConfigUnknownKeyException; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class GetConfigTest extends TestCase { + protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; + protected InputInterface&MockObject $consoleInput; + protected OutputInterface&MockObject $consoleOutput; + protected Command $command; + + protected function setUp(): void { + parent::setUp(); + + $this->appConfig = $this->createMock(IAppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new GetConfig($this->appConfig, $this->configManager); + } + + + public static function dataGet(): array { + return [ + // String output as json + ['name', 'newvalue', true, null, false, 'json', 0, json_encode('newvalue')], + // String output as plain text + ['name', 'newvalue', true, null, false, 'plain', 0, 'newvalue'], + // String falling back to default output as json + ['name', null, false, 'newvalue', true, 'json', 0, json_encode('newvalue')], + // String falling back without default: error + ['name', null, false, null, false, 'json', 1, null], + + // Int "0" output as json/plain + ['name', 0, true, null, false, 'json', 0, json_encode(0)], + ['name', 0, true, null, false, 'plain', 0, '0'], + // Int "1" output as json/plain + ['name', 1, true, null, false, 'json', 0, json_encode(1)], + ['name', 1, true, null, false, 'plain', 0, '1'], + + // Bool "true" output as json/plain + ['name', true, true, null, false, 'json', 0, json_encode(true)], + ['name', true, true, null, false, 'plain', 0, 'true'], + // Bool "false" output as json/plain + ['name', false, true, null, false, 'json', 0, json_encode(false)], + ['name', false, true, null, false, 'plain', 0, 'false'], + + // Null output as json/plain + ['name', null, true, null, false, 'json', 0, json_encode(null)], + ['name', null, true, null, false, 'plain', 0, 'null'], + + // Array output as json/plain + ['name', ['a', 'b'], true, null, false, 'json', 0, json_encode(['a', 'b'])], + ['name', ['a', 'b'], true, null, false, 'plain', 0, "a\nb"], + // Key array output as json/plain + ['name', [0 => 'a', 1 => 'b'], true, null, false, 'json', 0, json_encode(['a', 'b'])], + ['name', [0 => 'a', 1 => 'b'], true, null, false, 'plain', 0, "a\nb"], + // Associative array output as json/plain + ['name', ['a' => 1, 'b' => 2], true, null, false, 'json', 0, json_encode(['a' => 1, 'b' => 2])], + ['name', ['a' => 1, 'b' => 2], true, null, false, 'plain', 0, "a: 1\nb: 2"], + + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataGet')] + public function testGet(string $configName, mixed $value, bool $configExists, mixed $defaultValue, bool $hasDefault, string $outputFormat, int $expectedReturn, ?string $expectedMessage): void { + if (!$expectedReturn) { + if ($configExists) { + $this->appConfig->expects($this->once()) + ->method('getDetails') + ->with('app-name', $configName) + ->willReturn(['value' => $value]); + } + } + + if (!$configExists) { + $this->appConfig->expects($this->once()) + ->method('getDetails') + ->with('app-name', $configName) + ->willThrowException(new AppConfigUnknownKeyException()); + } + + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['app', 'app-name'], + ['name', $configName], + ]); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['default-value', $defaultValue], + ['output', $outputFormat], + ]); + $this->consoleInput->method('hasParameterOption') + ->willReturnMap([ + ['--output', false, true], + ['--default-value', false, $hasDefault], + ]); + + if ($expectedMessage !== null) { + global $output; + + $output = ''; + $this->consoleOutput->method('writeln') + ->willReturnCallback(function ($value) { + global $output; + $output .= $value . "\n"; + return $output; + }); + } + + $this->assertSame($expectedReturn, self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput])); + + if ($expectedMessage !== null) { + global $output; + // Remove the trailing newline + $this->assertSame($expectedMessage, substr($output, 0, -1)); + } + } +} diff --git a/tests/Core/Command/Config/App/SetConfigTest.php b/tests/Core/Command/Config/App/SetConfigTest.php new file mode 100644 index 00000000000..a5c62368163 --- /dev/null +++ b/tests/Core/Command/Config/App/SetConfigTest.php @@ -0,0 +1,107 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\App; + +use OC\AppConfig; +use OC\Config\ConfigManager; +use OC\Core\Command\Config\App\SetConfig; +use OCP\Exceptions\AppConfigUnknownKeyException; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class SetConfigTest extends TestCase { + protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; + protected InputInterface&MockObject $consoleInput; + protected OutputInterface&MockObject $consoleOutput; + protected Command $command; + + protected function setUp(): void { + parent::setUp(); + + $this->appConfig = $this->createMock(AppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new SetConfig($this->appConfig, $this->configManager); + } + + + public static function dataSet(): array { + return [ + [ + 'name', + 'newvalue', + true, + true, + true, + 'info', + ], + [ + 'name', + 'newvalue', + false, + true, + false, + 'comment', + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataSet')] + public function testSet(string $configName, mixed $newValue, bool $configExists, bool $updateOnly, bool $updated, string $expectedMessage): void { + $this->appConfig->method('hasKey') + ->with('app-name', $configName) + ->willReturn($configExists); + + if (!$configExists) { + $this->appConfig->method('getValueType') + ->willThrowException(new AppConfigUnknownKeyException()); + } else { + $this->appConfig->method('getValueType') + ->willReturn(IAppConfig::VALUE_MIXED); + } + + if ($updated) { + $this->appConfig->expects($this->once()) + ->method('setValueMixed') + ->with('app-name', $configName, $newValue); + } + + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['app', 'app-name'], + ['name', $configName], + ]); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['value', $newValue], + ['lazy', null], + ['sensitive', null], + ['no-interaction', true], + ]); + $this->consoleInput->method('hasParameterOption') + ->willReturnMap([ + ['--type', false, false], + ['--value', false, true], + ['--update-only', false, $updateOnly] + ]); + $this->consoleOutput->method('writeln') + ->with($this->stringContains($expectedMessage)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Config/ImportTest.php b/tests/Core/Command/Config/ImportTest.php new file mode 100644 index 00000000000..14cdd714d12 --- /dev/null +++ b/tests/Core/Command/Config/ImportTest.php @@ -0,0 +1,169 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config; + +use OC\Core\Command\Config\Import; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ImportTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var IConfig $config */ + $this->command = new Import($config); + } + + public static function validateAppsArrayData(): array { + return [ + [0], + [1], + [null], + ['new \Exception()'], + [json_encode([])], + ]; + } + + /** + * @param mixed $configValue + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validateAppsArrayData')] + public function testValidateAppsArray($configValue): void { + $this->invokePrivate($this->command, 'validateAppsArray', [['app' => ['name' => $configValue]]]); + $this->assertTrue(true, 'Asserting that no exception is thrown'); + } + + public static function validateAppsArrayThrowsData(): array { + return [ + [false], + [true], + [[]], + [new \Exception()], + ]; + } + + /** + * @param mixed $configValue + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validateAppsArrayThrowsData')] + public function testValidateAppsArrayThrows($configValue): void { + try { + $this->invokePrivate($this->command, 'validateAppsArray', [['app' => ['name' => $configValue]]]); + $this->fail('Did not throw expected UnexpectedValueException'); + } catch (\UnexpectedValueException $e) { + $this->assertStringStartsWith('Invalid app config value for "app":"name".', $e->getMessage()); + } + } + + public static function checkTypeRecursivelyData(): array { + return [ + [0], + [1], + [null], + ['new \Exception()'], + [json_encode([])], + [false], + [true], + [[]], + [['string']], + [['test' => 'string']], + [['test' => ['sub' => 'string']]], + ]; + } + + /** + * @param mixed $configValue + */ + #[\PHPUnit\Framework\Attributes\DataProvider('checkTypeRecursivelyData')] + public function testCheckTypeRecursively($configValue): void { + $this->invokePrivate($this->command, 'checkTypeRecursively', [$configValue, 'name']); + $this->assertTrue(true, 'Asserting that no exception is thrown'); + } + + public static function checkTypeRecursivelyThrowsData(): array { + return [ + [new \Exception()], + [[new \Exception()]], + [['test' => new \Exception()]], + [['test' => ['sub' => new \Exception()]]], + ]; + } + + /** + * @param mixed $configValue + */ + #[\PHPUnit\Framework\Attributes\DataProvider('checkTypeRecursivelyThrowsData')] + public function testCheckTypeRecursivelyThrows($configValue): void { + try { + $this->invokePrivate($this->command, 'checkTypeRecursively', [$configValue, 'name']); + $this->fail('Did not throw expected UnexpectedValueException'); + } catch (\UnexpectedValueException $e) { + $this->assertStringStartsWith('Invalid system config value for "name"', $e->getMessage()); + } + } + + public static function validateArrayData(): array { + return [ + [['system' => []]], + [['apps' => []]], + [['system' => [], 'apps' => []]], + ]; + } + + /** + * @param array $configArray + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validateArrayData')] + public function testValidateArray($configArray): void { + $this->invokePrivate($this->command, 'validateArray', [$configArray]); + $this->assertTrue(true, 'Asserting that no exception is thrown'); + } + + public static function validateArrayThrowsData(): array { + return [ + [[], 'At least one key of the following is expected:'], + [[0 => []], 'Found invalid entries in root'], + [['string' => []], 'Found invalid entries in root'], + ]; + } + + /** + * + * @param mixed $configArray + * @param string $expectedException + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validateArrayThrowsData')] + public function testValidateArrayThrows($configArray, $expectedException): void { + try { + $this->invokePrivate($this->command, 'validateArray', [$configArray]); + $this->fail('Did not throw expected UnexpectedValueException'); + } catch (\UnexpectedValueException $e) { + $this->assertStringStartsWith($expectedException, $e->getMessage()); + } + } +} diff --git a/tests/Core/Command/Config/ListConfigsTest.php b/tests/Core/Command/Config/ListConfigsTest.php new file mode 100644 index 00000000000..e4af55116c0 --- /dev/null +++ b/tests/Core/Command/Config/ListConfigsTest.php @@ -0,0 +1,328 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config; + +use OC\Config\ConfigManager; +use OC\Core\Command\Config\ListConfigs; +use OC\SystemConfig; +use OCP\IAppConfig; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ListConfigsTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $appConfig; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $systemConfig; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $configManager; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $systemConfig = $this->systemConfig = $this->getMockBuilder(SystemConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $appConfig = $this->appConfig = $this->getMockBuilder(IAppConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $configManager = $this->configManager = $this->getMockBuilder(ConfigManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var \OC\SystemConfig $systemConfig */ + /** @var \OCP\IAppConfig $appConfig */ + /** @var ConfigManager $configManager */ + $this->command = new ListConfigs($systemConfig, $appConfig, $configManager); + } + + public static function listData(): array { + return [ + [ + 'all', + // config.php + [ + 'secret', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', IConfig::SENSITIVE_VALUE], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', [ + 'enabled' => 'yes', + ]], + ['core', [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + false, + json_encode([ + 'system' => [ + 'secret' => IConfig::SENSITIVE_VALUE, + 'overwrite.cli.url' => 'http://localhost', + ], + 'apps' => [ + 'core' => [ + 'global_cache_gc_lastrun' => '1430388388', + ], + 'files' => [ + 'enabled' => 'yes', + ], + ], + ]), + ], + [ + 'all', + // config.php + [ + 'secret', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', 'my secret'], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', false, [ + 'enabled' => 'yes', + ]], + ['core', false, [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + true, + json_encode([ + 'system' => [ + 'secret' => 'my secret', + 'overwrite.cli.url' => 'http://localhost', + ], + 'apps' => [ + 'core' => [ + 'global_cache_gc_lastrun' => '1430388388', + ], + 'files' => [ + 'enabled' => 'yes', + ], + ], + ]), + ], + [ + 'system', + // config.php + [ + 'secret', + 'objectstore', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', IConfig::SENSITIVE_VALUE], + ['objectstore', 'N;', [ + 'class' => 'OC\\Files\\ObjectStore\\Swift', + 'arguments' => [ + 'username' => 'facebook100000123456789', + 'password' => IConfig::SENSITIVE_VALUE, + ], + ]], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', [ + 'enabled' => 'yes', + ]], + ['core', [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + false, + json_encode([ + 'system' => [ + 'secret' => IConfig::SENSITIVE_VALUE, + 'objectstore' => [ + 'class' => 'OC\\Files\\ObjectStore\\Swift', + 'arguments' => [ + 'username' => 'facebook100000123456789', + 'password' => IConfig::SENSITIVE_VALUE, + ], + ], + 'overwrite.cli.url' => 'http://localhost', + ], + ]), + ], + [ + 'system', + // config.php + [ + 'secret', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', 'my secret'], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', [ + 'enabled' => 'yes', + ]], + ['core', [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + true, + json_encode([ + 'system' => [ + 'secret' => 'my secret', + 'overwrite.cli.url' => 'http://localhost', + ], + ]), + ], + [ + 'files', + // config.php + [ + 'secret', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', 'my secret'], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', [ + 'enabled' => 'yes', + ]], + ['core', [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + false, + json_encode([ + 'apps' => [ + 'files' => [ + 'enabled' => 'yes', + ], + ], + ]), + ], + [ + 'files', + // config.php + [ + 'secret', + 'overwrite.cli.url', + ], + [ + ['secret', 'N;', 'my secret'], + ['overwrite.cli.url', 'N;', 'http://localhost'], + ], + // app config + [ + ['files', false, [ + 'enabled' => 'yes', + ]], + ['core', false, [ + 'global_cache_gc_lastrun' => '1430388388', + ]], + ], + true, + json_encode([ + 'apps' => [ + 'files' => [ + 'enabled' => 'yes', + ], + ], + ]), + ], + ]; + } + + /** + * + * @param string $app + * @param array $systemConfigs + * @param array $systemConfigMap + * @param array $appConfig + * @param bool $private + * @param string $expected + */ + #[\PHPUnit\Framework\Attributes\DataProvider('listData')] + public function testList($app, $systemConfigs, $systemConfigMap, $appConfig, $private, $expected): void { + $this->systemConfig->expects($this->any()) + ->method('getKeys') + ->willReturn($systemConfigs); + if ($private) { + $this->systemConfig->expects($this->any()) + ->method('getValue') + ->willReturnMap($systemConfigMap); + $this->appConfig->expects($this->any()) + ->method('getValues') + ->willReturnMap($appConfig); + } else { + $this->systemConfig->expects($this->any()) + ->method('getFilteredValue') + ->willReturnMap($systemConfigMap); + $this->appConfig->expects($this->any()) + ->method('getFilteredValues') + ->willReturnMap($appConfig); + } + + $this->appConfig->expects($this->any()) + ->method('getApps') + ->willReturn(['core', 'files']); + $this->appConfig->expects($this->any()) + ->method('getValues') + ->willReturnMap($appConfig); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('app') + ->willReturn($app); + + $this->consoleInput->expects($this->any()) + ->method('getOption') + ->willReturnMap([ + ['output', 'json'], + ['private', $private], + ]); + + global $output; + + $output = ''; + $this->consoleOutput->expects($this->any()) + ->method('writeln') + ->willReturnCallback(function ($value) { + global $output; + $output .= $value . "\n"; + return $output; + }); + + $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + + $this->assertEquals($expected, trim($output, "\n")); + } +} diff --git a/tests/Core/Command/Config/System/CastHelperTest.php b/tests/Core/Command/Config/System/CastHelperTest.php new file mode 100644 index 00000000000..924887daaf7 --- /dev/null +++ b/tests/Core/Command/Config/System/CastHelperTest.php @@ -0,0 +1,66 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Core\Command\Config\System; + +use OC\Core\Command\Config\System\CastHelper; +use Test\TestCase; + +class CastHelperTest extends TestCase { + private CastHelper $castHelper; + + protected function setUp(): void { + parent::setUp(); + $this->castHelper = new CastHelper(); + } + + public static function castValueProvider(): array { + return [ + [null, 'string', ['value' => '', 'readable-value' => 'empty string']], + + ['abc', 'string', ['value' => 'abc', 'readable-value' => 'string abc']], + + ['123', 'integer', ['value' => 123, 'readable-value' => 'integer 123']], + ['456', 'int', ['value' => 456, 'readable-value' => 'integer 456']], + + ['2.25', 'double', ['value' => 2.25, 'readable-value' => 'double 2.25']], + ['0.5', 'float', ['value' => 0.5, 'readable-value' => 'double 0.5']], + + ['', 'null', ['value' => null, 'readable-value' => 'null']], + + ['true', 'boolean', ['value' => true, 'readable-value' => 'boolean true']], + ['false', 'bool', ['value' => false, 'readable-value' => 'boolean false']], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('castValueProvider')] + public function testCastValue($value, $type, $expectedValue): void { + $this->assertSame( + $expectedValue, + $this->castHelper->castValue($value, $type) + ); + } + + public static function castValueInvalidProvider(): array { + return [ + ['123', 'foobar'], + + [null, 'integer'], + ['abc', 'integer'], + ['76ggg', 'double'], + ['true', 'float'], + ['foobar', 'boolean'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('castValueInvalidProvider')] + public function testCastValueInvalid($value, $type): void { + $this->expectException(\InvalidArgumentException::class); + + $this->castHelper->castValue($value, $type); + } +} diff --git a/tests/Core/Command/Config/System/DeleteConfigTest.php b/tests/Core/Command/Config/System/DeleteConfigTest.php new file mode 100644 index 00000000000..b0a3580e1cd --- /dev/null +++ b/tests/Core/Command/Config/System/DeleteConfigTest.php @@ -0,0 +1,207 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\System; + +use OC\Core\Command\Config\System\DeleteConfig; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteConfigTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $systemConfig; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $systemConfig = $this->systemConfig = $this->getMockBuilder(SystemConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var SystemConfig $systemConfig */ + $this->command = new DeleteConfig($systemConfig); + } + + public static function deleteData(): array { + return [ + [ + 'name1', + true, + true, + 0, + 'info', + ], + [ + 'name2', + true, + false, + 0, + 'info', + ], + [ + 'name3', + false, + false, + 0, + 'info', + ], + [ + 'name4', + false, + true, + 1, + 'error', + ], + ]; + } + + /** + * + * @param string $configName + * @param bool $configExists + * @param bool $checkIfExists + * @param int $expectedReturn + * @param string $expectedMessage + */ + #[\PHPUnit\Framework\Attributes\DataProvider('deleteData')] + public function testDelete($configName, $configExists, $checkIfExists, $expectedReturn, $expectedMessage): void { + $this->systemConfig->expects(($checkIfExists) ? $this->once() : $this->never()) + ->method('getKeys') + ->willReturn($configExists ? [$configName] : []); + + $this->systemConfig->expects(($expectedReturn === 0) ? $this->once() : $this->never()) + ->method('deleteValue') + ->with($configName); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn([$configName]); + $this->consoleInput->expects($this->any()) + ->method('hasParameterOption') + ->with('--error-if-not-exists') + ->willReturn($checkIfExists); + + $this->consoleOutput->expects($this->any()) + ->method('writeln') + ->with($this->stringContains($expectedMessage)); + + $this->assertSame($expectedReturn, $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function deleteArrayData(): array { + return [ + [ + ['name', 'sub'], + true, + false, + true, + true, + 0, + 'info', + ], + [ + ['name', 'sub', '2sub'], + true, + false, + ['sub' => ['2sub' => 1], 'sub2' => false], + ['sub' => [], 'sub2' => false], + 0, + 'info', + ], + [ + ['name', 'sub3'], + true, + false, + ['sub' => ['2sub' => 1], 'sub2' => false], + ['sub' => ['2sub' => 1], 'sub2' => false], + 0, + 'info', + ], + [ + ['name', 'sub'], + false, + true, + true, + true, + 1, + 'error', + ], + [ + ['name', 'sub'], + true, + true, + true, + true, + 1, + 'error', + ], + [ + ['name', 'sub3'], + true, + true, + ['sub' => ['2sub' => 1], 'sub2' => false], + ['sub' => ['2sub' => 1], 'sub2' => false], + 1, + 'error', + ], + ]; + } + + /** + * + * @param string[] $configNames + * @param bool $configKeyExists + * @param bool $checkIfKeyExists + * @param mixed $configValue + * @param mixed $updateValue + * @param int $expectedReturn + * @param string $expectedMessage + */ + #[\PHPUnit\Framework\Attributes\DataProvider('deleteArrayData')] + public function testArrayDelete(array $configNames, $configKeyExists, $checkIfKeyExists, $configValue, $updateValue, $expectedReturn, $expectedMessage): void { + $this->systemConfig->expects(($checkIfKeyExists) ? $this->once() : $this->never()) + ->method('getKeys') + ->willReturn($configKeyExists ? [$configNames[0]] : []); + + $this->systemConfig->expects(($configKeyExists) ? $this->once() : $this->never()) + ->method('getValue') + ->willReturn($configValue); + + $this->systemConfig->expects(($expectedReturn === 0) ? $this->once() : $this->never()) + ->method('setValue') + ->with($configNames[0], $updateValue); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn($configNames); + $this->consoleInput->expects($this->any()) + ->method('hasParameterOption') + ->with('--error-if-not-exists') + ->willReturn($checkIfKeyExists); + + $this->consoleOutput->expects($this->any()) + ->method('writeln') + ->with($this->stringContains($expectedMessage)); + + $this->assertSame($expectedReturn, $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } +} diff --git a/tests/Core/Command/Config/System/GetConfigTest.php b/tests/Core/Command/Config/System/GetConfigTest.php new file mode 100644 index 00000000000..8b84fd14198 --- /dev/null +++ b/tests/Core/Command/Config/System/GetConfigTest.php @@ -0,0 +1,161 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\System; + +use OC\Core\Command\Config\System\GetConfig; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class GetConfigTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $systemConfig; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $systemConfig = $this->systemConfig = $this->getMockBuilder(SystemConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var SystemConfig $systemConfig */ + $this->command = new GetConfig($systemConfig); + } + + + public static function getData(): array { + return [ + // String output as json + ['name', 'newvalue', true, null, false, 'json', 0, json_encode('newvalue')], + // String output as plain text + ['name', 'newvalue', true, null, false, 'plain', 0, 'newvalue'], + // String falling back to default output as json + ['name', null, false, 'newvalue', true, 'json', 0, json_encode('newvalue')], + // String falling back without default: error + ['name', null, false, null, false, 'json', 1, null], + + // Int "0" output as json/plain + ['name', 0, true, null, false, 'json', 0, json_encode(0)], + ['name', 0, true, null, false, 'plain', 0, '0'], + // Int "1" output as json/plain + ['name', 1, true, null, false, 'json', 0, json_encode(1)], + ['name', 1, true, null, false, 'plain', 0, '1'], + + // Bool "true" output as json/plain + ['name', true, true, null, false, 'json', 0, json_encode(true)], + ['name', true, true, null, false, 'plain', 0, 'true'], + // Bool "false" output as json/plain + ['name', false, true, null, false, 'json', 0, json_encode(false)], + ['name', false, true, null, false, 'plain', 0, 'false'], + + // Null output as json/plain + ['name', null, true, null, false, 'json', 0, json_encode(null)], + ['name', null, true, null, false, 'plain', 0, 'null'], + + // Array output as json/plain + ['name', ['a', 'b'], true, null, false, 'json', 0, json_encode(['a', 'b'])], + ['name', ['a', 'b'], true, null, false, 'plain', 0, "a\nb"], + // Key array output as json/plain + ['name', [0 => 'a', 1 => 'b'], true, null, false, 'json', 0, json_encode(['a', 'b'])], + ['name', [0 => 'a', 1 => 'b'], true, null, false, 'plain', 0, "a\nb"], + // Associative array output as json/plain + ['name', ['a' => 1, 'b' => 2], true, null, false, 'json', 0, json_encode(['a' => 1, 'b' => 2])], + ['name', ['a' => 1, 'b' => 2], true, null, false, 'plain', 0, "a: 1\nb: 2"], + + // Nested depth + [['name', 'a'], ['a' => 1, 'b' => 2], true, null, false, 'json', 0, json_encode(1)], + [['name', 'a'], ['a' => 1, 'b' => 2], true, null, false, 'plain', 0, '1'], + [['name', 'c'], ['a' => 1, 'b' => 2], true, true, true, 'json', 0, json_encode(true)], + [['name', 'c'], ['a' => 1, 'b' => 2], true, true, false, 'json', 1, null], + + ]; + } + + /** + * + * @param string[] $configNames + * @param mixed $value + * @param bool $configExists + * @param mixed $defaultValue + * @param bool $hasDefault + * @param string $outputFormat + * @param int $expectedReturn + * @param string $expectedMessage + */ + #[\PHPUnit\Framework\Attributes\DataProvider('getData')] + public function testGet($configNames, $value, $configExists, $defaultValue, $hasDefault, $outputFormat, $expectedReturn, $expectedMessage): void { + if (is_array($configNames)) { + $configName = $configNames[0]; + } else { + $configName = $configNames; + $configNames = [$configName]; + } + $this->systemConfig->expects($this->atLeastOnce()) + ->method('getKeys') + ->willReturn($configExists ? [$configName] : []); + + if (!$expectedReturn) { + if ($configExists) { + $this->systemConfig->expects($this->once()) + ->method('getValue') + ->with($configName) + ->willReturn($value); + } + } + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn($configNames); + $this->consoleInput->expects($this->any()) + ->method('getOption') + ->willReturnMap([ + ['default-value', $defaultValue], + ['output', $outputFormat], + ]); + $this->consoleInput->expects($this->any()) + ->method('hasParameterOption') + ->willReturnMap([ + ['--output', false, true], + ['--default-value', false,$hasDefault], + ]); + + if ($expectedMessage !== null) { + global $output; + + $output = ''; + $this->consoleOutput->expects($this->any()) + ->method('writeln') + ->willReturnCallback(function ($value) { + global $output; + $output .= $value . "\n"; + return $output; + }); + } + + $this->assertSame($expectedReturn, $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput])); + + if ($expectedMessage !== null) { + global $output; + // Remove the trailing newline + $this->assertSame($expectedMessage, substr($output, 0, -1)); + } + } +} diff --git a/tests/Core/Command/Config/System/SetConfigTest.php b/tests/Core/Command/Config/System/SetConfigTest.php new file mode 100644 index 00000000000..a99b832c160 --- /dev/null +++ b/tests/Core/Command/Config/System/SetConfigTest.php @@ -0,0 +1,115 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Config\System; + +use OC\Core\Command\Config\System\CastHelper; +use OC\Core\Command\Config\System\SetConfig; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class SetConfigTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $systemConfig; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $systemConfig = $this->systemConfig = $this->getMockBuilder(SystemConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var SystemConfig $systemConfig */ + $this->command = new SetConfig($systemConfig, new CastHelper()); + } + + + public static function dataTest() { + return [ + [['name'], 'newvalue', null, 'newvalue'], + [['a', 'b', 'c'], 'foobar', null, ['b' => ['c' => 'foobar']]], + [['a', 'b', 'c'], 'foobar', ['b' => ['d' => 'barfoo']], ['b' => ['d' => 'barfoo', 'c' => 'foobar']]], + ]; + } + + /** + * + * @param array $configNames + * @param string $newValue + * @param mixed $existingData + * @param mixed $expectedValue + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTest')] + public function testSet($configNames, $newValue, $existingData, $expectedValue): void { + $this->systemConfig->expects($this->once()) + ->method('setValue') + ->with($configNames[0], $expectedValue); + $this->systemConfig->method('getValue') + ->with($configNames[0]) + ->willReturn($existingData); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn($configNames); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['value', $newValue], + ['type', 'string'], + ]); + + $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public static function setUpdateOnlyProvider(): array { + return [ + [['name'], null], + [['a', 'b', 'c'], null], + [['a', 'b', 'c'], ['b' => 'foobar']], + [['a', 'b', 'c'], ['b' => ['d' => 'foobar']]], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('setUpdateOnlyProvider')] + public function testSetUpdateOnly($configNames, $existingData): void { + $this->expectException(\UnexpectedValueException::class); + + $this->systemConfig->expects($this->never()) + ->method('setValue'); + $this->systemConfig->method('getValue') + ->with($configNames[0]) + ->willReturn($existingData); + $this->systemConfig->method('getKeys') + ->willReturn($existingData ? $configNames[0] : []); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn($configNames); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['value', 'foobar'], + ['type', 'string'], + ['update-only', true], + ]); + + $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php b/tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php new file mode 100644 index 00000000000..0bc6cbb64cf --- /dev/null +++ b/tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php @@ -0,0 +1,360 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\ChangeKeyStorageRoot; +use OC\Encryption\Keys\Storage; +use OC\Encryption\Util; +use OC\Files\View; +use OCP\IConfig; +use OCP\IUserManager; +use OCP\UserInterface; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ChangeKeyStorageRootTest extends TestCase { + /** @var ChangeKeyStorageRoot */ + protected $changeKeyStorageRoot; + + /** @var View | \PHPUnit\Framework\MockObject\MockObject */ + protected $view; + + /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + + /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var Util | \PHPUnit\Framework\MockObject\MockObject */ + protected $util; + + /** @var QuestionHelper | \PHPUnit\Framework\MockObject\MockObject */ + protected $questionHelper; + + /** @var InputInterface | \PHPUnit\Framework\MockObject\MockObject */ + protected $inputInterface; + + /** @var OutputInterface | \PHPUnit\Framework\MockObject\MockObject */ + protected $outputInterface; + + /** @var UserInterface|\PHPUnit\Framework\MockObject\MockObject */ + protected $userInterface; + + protected function setUp(): void { + parent::setUp(); + + $this->view = $this->getMockBuilder(View::class)->getMock(); + $this->userManager = $this->getMockBuilder(IUserManager::class)->getMock(); + $this->config = $this->getMockBuilder(IConfig::class)->getMock(); + $this->util = $this->getMockBuilder('OC\Encryption\Util')->disableOriginalConstructor()->getMock(); + $this->questionHelper = $this->getMockBuilder(QuestionHelper::class)->getMock(); + $this->inputInterface = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->outputInterface = $this->getMockBuilder(OutputInterface::class)->getMock(); + $this->userInterface = $this->getMockBuilder(UserInterface::class)->getMock(); + + /* We need format method to return a string */ + $outputFormatter = $this->createMock(OutputFormatterInterface::class); + $outputFormatter->method('isDecorated')->willReturn(false); + $outputFormatter->method('format')->willReturnArgument(0); + + $this->outputInterface->expects($this->any())->method('getFormatter') + ->willReturn($outputFormatter); + + $this->changeKeyStorageRoot = new ChangeKeyStorageRoot( + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestExecute')] + public function testExecute($newRoot, $answer, $successMoveKey): void { + $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot') + ->setConstructorArgs( + [ + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ] + )->onlyMethods(['moveAllKeys'])->getMock(); + + $this->util->expects($this->once())->method('getKeyStorageRoot') + ->willReturn(''); + $this->inputInterface->expects($this->once())->method('getArgument') + ->with('newRoot')->willReturn($newRoot); + + if ($answer === true || $newRoot !== null) { + $changeKeyStorageRoot->expects($this->once())->method('moveAllKeys') + ->willReturn($successMoveKey); + } else { + $changeKeyStorageRoot->expects($this->never())->method('moveAllKeys'); + } + + if ($successMoveKey === true) { + $this->util->expects($this->once())->method('setKeyStorageRoot'); + } else { + $this->util->expects($this->never())->method('setKeyStorageRoot'); + } + + if ($newRoot === null) { + $this->questionHelper->expects($this->once())->method('ask')->willReturn($answer); + } else { + $this->questionHelper->expects($this->never())->method('ask'); + } + + $this->invokePrivate( + $changeKeyStorageRoot, + 'execute', + [$this->inputInterface, $this->outputInterface] + ); + } + + public static function dataTestExecute(): array { + return [ + [null, true, true], + [null, true, false], + [null, false, null], + ['/newRoot', null, true], + ['/newRoot', null, false] + ]; + } + + public function testMoveAllKeys(): void { + /** @var ChangeKeyStorageRoot $changeKeyStorageRoot */ + $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot') + ->setConstructorArgs( + [ + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ] + )->onlyMethods(['prepareNewRoot', 'moveSystemKeys', 'moveUserKeys'])->getMock(); + + $changeKeyStorageRoot->expects($this->once())->method('prepareNewRoot')->with('newRoot'); + $changeKeyStorageRoot->expects($this->once())->method('moveSystemKeys')->with('oldRoot', 'newRoot'); + $changeKeyStorageRoot->expects($this->once())->method('moveUserKeys')->with('oldRoot', 'newRoot', $this->outputInterface); + + $this->invokePrivate($changeKeyStorageRoot, 'moveAllKeys', ['oldRoot', 'newRoot', $this->outputInterface]); + } + + public function testPrepareNewRoot(): void { + $this->view->expects($this->once())->method('is_dir')->with('newRoot') + ->willReturn(true); + + $this->view->expects($this->once())->method('file_put_contents') + ->with('newRoot/' . Storage::KEY_STORAGE_MARKER, + 'Nextcloud will detect this folder as key storage root only if this file exists')->willReturn(true); + + $this->invokePrivate($this->changeKeyStorageRoot, 'prepareNewRoot', ['newRoot']); + } + + /** + * + * @param bool $dirExists + * @param bool $couldCreateFile + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestPrepareNewRootException')] + public function testPrepareNewRootException($dirExists, $couldCreateFile): void { + $this->expectException(\Exception::class); + + $this->view->expects($this->once())->method('is_dir')->with('newRoot') + ->willReturn($dirExists); + $this->view->expects($this->any())->method('file_put_contents')->willReturn($couldCreateFile); + + $this->invokePrivate($this->changeKeyStorageRoot, 'prepareNewRoot', ['newRoot']); + } + + public static function dataTestPrepareNewRootException(): array { + return [ + [true, false], + [true, null], + [false, true] + ]; + } + + /** + * + * @param bool $dirExists + * @param bool $targetExists + * @param bool $executeRename + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestMoveSystemKeys')] + public function testMoveSystemKeys($dirExists, $targetExists, $executeRename): void { + $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot') + ->setConstructorArgs( + [ + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ] + )->onlyMethods(['targetExists'])->getMock(); + + $this->view->expects($this->once())->method('is_dir') + ->with('oldRoot/files_encryption')->willReturn($dirExists); + $changeKeyStorageRoot->expects($this->any())->method('targetExists') + ->with('newRoot/files_encryption')->willReturn($targetExists); + + if ($executeRename) { + $this->view->expects($this->once())->method('rename') + ->with('oldRoot/files_encryption', 'newRoot/files_encryption'); + } else { + $this->view->expects($this->never())->method('rename'); + } + + $this->invokePrivate($changeKeyStorageRoot, 'moveSystemKeys', ['oldRoot', 'newRoot']); + } + + public static function dataTestMoveSystemKeys(): array { + return [ + [true, false, true], + [false, true, false], + [true, true, false], + [false, false, false] + ]; + } + + + public function testMoveUserKeys(): void { + $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot') + ->setConstructorArgs( + [ + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ] + )->onlyMethods(['setupUserFS', 'moveUserEncryptionFolder'])->getMock(); + + $this->userManager->expects($this->once())->method('getBackends') + ->willReturn([$this->userInterface]); + $this->userInterface->expects($this->once())->method('getUsers') + ->willReturn(['user1', 'user2']); + $changeKeyStorageRoot->expects($this->exactly(2))->method('setupUserFS'); + $changeKeyStorageRoot->expects($this->exactly(2))->method('moveUserEncryptionFolder'); + + $this->invokePrivate($changeKeyStorageRoot, 'moveUserKeys', ['oldRoot', 'newRoot', $this->outputInterface]); + } + + /** + * + * @param bool $userExists + * @param bool $isDir + * @param bool $targetExists + * @param bool $shouldRename + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestMoveUserEncryptionFolder')] + public function testMoveUserEncryptionFolder($userExists, $isDir, $targetExists, $shouldRename): void { + $changeKeyStorageRoot = $this->getMockBuilder('OC\Core\Command\Encryption\ChangeKeyStorageRoot') + ->setConstructorArgs( + [ + $this->view, + $this->userManager, + $this->config, + $this->util, + $this->questionHelper + ] + )->onlyMethods(['targetExists', 'prepareParentFolder'])->getMock(); + + $this->userManager->expects($this->once())->method('userExists') + ->willReturn($userExists); + $this->view->expects($this->any())->method('is_dir') + ->willReturn($isDir); + $changeKeyStorageRoot->expects($this->any())->method('targetExists') + ->willReturn($targetExists); + + if ($shouldRename) { + $changeKeyStorageRoot->expects($this->once())->method('prepareParentFolder') + ->with('newRoot/user1'); + $this->view->expects($this->once())->method('rename') + ->with('oldRoot/user1/files_encryption', 'newRoot/user1/files_encryption'); + } else { + $changeKeyStorageRoot->expects($this->never())->method('prepareParentFolder'); + $this->view->expects($this->never())->method('rename'); + } + + $this->invokePrivate($changeKeyStorageRoot, 'moveUserEncryptionFolder', ['user1', 'oldRoot', 'newRoot']); + } + + public static function dataTestMoveUserEncryptionFolder(): array { + return [ + [true, true, false, true], + [true, false, true, false], + [false, true, true, false], + [false, false, true, false], + [false, true, false, false], + [false, true, true, false], + [false, false, false, false] + ]; + } + + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestPrepareParentFolder')] + public function testPrepareParentFolder($path, $pathExists): void { + $this->view->expects($this->any())->method('file_exists') + ->willReturnCallback( + function ($fileExistsPath) use ($path, $pathExists) { + if ($path === $fileExistsPath) { + return $pathExists; + } + return false; + } + ); + + if ($pathExists === false) { + $subDirs = explode('/', ltrim($path, '/')); + $this->view->expects($this->exactly(count($subDirs)))->method('mkdir'); + } else { + $this->view->expects($this->never())->method('mkdir'); + } + + $this->invokePrivate( + $this->changeKeyStorageRoot, + 'prepareParentFolder', + [$path] + ); + } + + public static function dataTestPrepareParentFolder(): array { + return [ + ['/user/folder/sub_folder/keystorage', true], + ['/user/folder/sub_folder/keystorage', false] + ]; + } + + public function testTargetExists(): void { + $this->view->expects($this->once())->method('file_exists')->with('path') + ->willReturn(false); + + $this->assertFalse( + $this->invokePrivate($this->changeKeyStorageRoot, 'targetExists', ['path']) + ); + } + + + public function testTargetExistsException(): void { + $this->expectException(\Exception::class); + + $this->view->expects($this->once())->method('file_exists')->with('path') + ->willReturn(true); + + $this->invokePrivate($this->changeKeyStorageRoot, 'targetExists', ['path']); + } +} diff --git a/tests/Core/Command/Encryption/DecryptAllTest.php b/tests/Core/Command/Encryption/DecryptAllTest.php new file mode 100644 index 00000000000..41d9e4c713f --- /dev/null +++ b/tests/Core/Command/Encryption/DecryptAllTest.php @@ -0,0 +1,216 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\DecryptAll; +use OCP\App\IAppManager; +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DecryptAllTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject|IConfig */ + protected $config; + + /** @var \PHPUnit\Framework\MockObject\MockObject | \OCP\Encryption\IManager */ + protected $encryptionManager; + + /** @var \PHPUnit\Framework\MockObject\MockObject|IAppManager */ + protected $appManager; + + /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Input\InputInterface */ + protected $consoleInput; + + /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Output\OutputInterface */ + protected $consoleOutput; + + /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Helper\QuestionHelper */ + protected $questionHelper; + + /** @var \PHPUnit\Framework\MockObject\MockObject | \OC\Encryption\DecryptAll */ + protected $decryptAll; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->encryptionManager = $this->getMockBuilder(IManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->appManager = $this->getMockBuilder(IAppManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->questionHelper = $this->getMockBuilder(QuestionHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->decryptAll = $this->getMockBuilder(\OC\Encryption\DecryptAll::class) + ->disableOriginalConstructor()->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleInput->expects($this->any()) + ->method('isInteractive') + ->willReturn(true); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + $this->config->expects($this->any()) + ->method('getSystemValue') + ->with('maintenance', false) + ->willReturn(false); + $this->appManager->expects($this->any()) + ->method('isEnabledForUser') + ->with('files_trashbin')->willReturn(true); + } + + public function testMaintenanceAndTrashbin(): void { + // on construct we enable single-user-mode and disable the trash bin + // on destruct we disable single-user-mode again and enable the trash bin + $calls = [ + ['maintenance', true], + ['maintenance', false], + ]; + $this->config->expects($this->exactly(2)) + ->method('setSystemValue') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + $this->appManager->expects($this->once()) + ->method('disableApp') + ->with('files_trashbin'); + $this->appManager->expects($this->once()) + ->method('enableApp') + ->with('files_trashbin'); + + $instance = new DecryptAll( + $this->encryptionManager, + $this->appManager, + $this->config, + $this->decryptAll, + $this->questionHelper + ); + $this->invokePrivate($instance, 'forceMaintenanceAndTrashbin'); + + $this->assertTrue( + $this->invokePrivate($instance, 'wasTrashbinEnabled') + ); + + $this->assertFalse( + $this->invokePrivate($instance, 'wasMaintenanceModeEnabled') + ); + $this->invokePrivate($instance, 'resetMaintenanceAndTrashbin'); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestExecute')] + public function testExecute($encryptionEnabled, $continue): void { + $instance = new DecryptAll( + $this->encryptionManager, + $this->appManager, + $this->config, + $this->decryptAll, + $this->questionHelper + ); + + $this->encryptionManager->expects($this->once()) + ->method('isEnabled') + ->willReturn($encryptionEnabled); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->with('user') + ->willReturn('user1'); + + if ($encryptionEnabled) { + $calls = [ + ['core', 'encryption_enabled', 'no'], + ['core', 'encryption_enabled', 'yes'], + ]; + $this->config->expects($this->exactly(2)) + ->method('setAppValue') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + $this->questionHelper->expects($this->once()) + ->method('ask') + ->willReturn($continue); + if ($continue) { + $this->decryptAll->expects($this->once()) + ->method('decryptAll') + ->with($this->consoleInput, $this->consoleOutput, 'user1'); + } else { + $this->decryptAll->expects($this->never())->method('decryptAll'); + } + } else { + $this->config->expects($this->never())->method('setAppValue'); + $this->decryptAll->expects($this->never())->method('decryptAll'); + $this->questionHelper->expects($this->never())->method('ask'); + } + + $this->invokePrivate($instance, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public static function dataTestExecute(): array { + return [ + [true, true], + [true, false], + [false, true], + [false, false] + ]; + } + + + public function testExecuteFailure(): void { + $this->expectException(\Exception::class); + + $instance = new DecryptAll( + $this->encryptionManager, + $this->appManager, + $this->config, + $this->decryptAll, + $this->questionHelper + ); + + // make sure that we enable encryption again after a exception was thrown + $calls = [ + ['core', 'encryption_enabled', 'no'], + ['core', 'encryption_enabled', 'yes'], + ]; + $this->config->expects($this->exactly(2)) + ->method('setAppValue') + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + $this->encryptionManager->expects($this->once()) + ->method('isEnabled') + ->willReturn(true); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->with('user') + ->willReturn('user1'); + + $this->questionHelper->expects($this->once()) + ->method('ask') + ->willReturn(true); + + $this->decryptAll->expects($this->once()) + ->method('decryptAll') + ->with($this->consoleInput, $this->consoleOutput, 'user1') + ->willReturnCallback(function (): void { + throw new \Exception(); + }); + + $this->invokePrivate($instance, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Encryption/DisableTest.php b/tests/Core/Command/Encryption/DisableTest.php new file mode 100644 index 00000000000..a89fd636e47 --- /dev/null +++ b/tests/Core/Command/Encryption/DisableTest.php @@ -0,0 +1,74 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\Disable; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DisableTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var IConfig $config */ + $this->command = new Disable($config); + } + + + public static function dataDisable(): array { + return [ + ['yes', true, 'Encryption disabled'], + ['no', false, 'Encryption is already disabled'], + ]; + } + + /** + * + * @param string $oldStatus + * @param bool $isUpdating + * @param string $expectedString + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataDisable')] + public function testDisable($oldStatus, $isUpdating, $expectedString): void { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('core', 'encryption_enabled', $this->anything()) + ->willReturn($oldStatus); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains($expectedString)); + + if ($isUpdating) { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('core', 'encryption_enabled', 'no'); + } + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Encryption/EnableTest.php b/tests/Core/Command/Encryption/EnableTest.php new file mode 100644 index 00000000000..32d1a7576f5 --- /dev/null +++ b/tests/Core/Command/Encryption/EnableTest.php @@ -0,0 +1,101 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\Enable; +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class EnableTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $manager; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $manager = $this->manager = $this->getMockBuilder(IManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var \OCP\IConfig $config */ + /** @var \OCP\Encryption\IManager $manager */ + $this->command = new Enable($config, $manager); + } + + + public static function dataEnable(): array { + return [ + ['no', null, [], true, 'Encryption enabled', 'No encryption module is loaded'], + ['yes', null, [], false, 'Encryption is already enabled', 'No encryption module is loaded'], + ['no', null, ['OC_TEST_MODULE' => []], true, 'Encryption enabled', 'No default module is set'], + ['no', 'OC_NO_MODULE', ['OC_TEST_MODULE' => []], true, 'Encryption enabled', 'The current default module does not exist: OC_NO_MODULE'], + ['no', 'OC_TEST_MODULE', ['OC_TEST_MODULE' => []], true, 'Encryption enabled', 'Default module: OC_TEST_MODULE'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataEnable')] + public function testEnable(string $oldStatus, ?string $defaultModule, array $availableModules, bool $isUpdating, string $expectedString, string $expectedDefaultModuleString): void { + if ($isUpdating) { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('core', 'encryption_enabled', 'yes'); + } + + $this->manager->expects($this->atLeastOnce()) + ->method('getEncryptionModules') + ->willReturn($availableModules); + + if (empty($availableModules)) { + $this->config->expects($this->once()) + ->method('getAppValue') + ->willReturnMap([ + ['core', 'encryption_enabled', 'no', $oldStatus], + ]); + } else { + $this->config->expects($this->exactly(2)) + ->method('getAppValue') + ->willReturnMap([ + ['core', 'encryption_enabled', 'no', $oldStatus], + ['core', 'default_encryption_module', null, $defaultModule], + ]); + } + + $calls = [ + [$expectedString, 0], + ['', 0], + [$expectedDefaultModuleString, 0], + ]; + $this->consoleOutput->expects($this->exactly(3)) + ->method('writeln') + ->willReturnCallback(function (string $message, int $level) use (&$calls): void { + $call = array_shift($calls); + $this->assertStringContainsString($call[0], $message); + $this->assertSame($call[1], $level); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Encryption/EncryptAllTest.php b/tests/Core/Command/Encryption/EncryptAllTest.php new file mode 100644 index 00000000000..15cbe83737d --- /dev/null +++ b/tests/Core/Command/Encryption/EncryptAllTest.php @@ -0,0 +1,94 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\EncryptAll; +use OCP\App\IAppManager; +use OCP\Encryption\IEncryptionModule; +use OCP\Encryption\IManager; +use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class EncryptAllTest extends TestCase { + private IConfig&MockObject $config; + private IManager&MockObject $encryptionManager; + private IAppManager&MockObject $appManager; + private InputInterface&MockObject $consoleInput; + private OutputInterface&MockObject $consoleOutput; + private QuestionHelper&MockObject $questionHelper; + private IEncryptionModule&MockObject $encryptionModule; + + private EncryptAll $command; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->encryptionManager = $this->createMock(IManager::class); + $this->appManager = $this->createMock(IAppManager::class); + $this->encryptionModule = $this->createMock(IEncryptionModule::class); + $this->questionHelper = $this->createMock(QuestionHelper::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleInput->expects($this->any()) + ->method('isInteractive') + ->willReturn(true); + $this->consoleOutput = $this->createMock(OutputInterface::class); + } + + public function testEncryptAll(): void { + // trash bin needs to be disabled in order to avoid adding dummy files to the users + // trash bin which gets deleted during the encryption process + $this->appManager->expects($this->once())->method('disableApp')->with('files_trashbin'); + + $instance = new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper); + $this->invokePrivate($instance, 'forceMaintenanceAndTrashbin'); + $this->invokePrivate($instance, 'resetMaintenanceAndTrashbin'); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestExecute')] + public function testExecute($answer, $askResult): void { + $command = new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper); + + $this->encryptionManager->expects($this->once())->method('isEnabled')->willReturn(true); + $this->questionHelper->expects($this->once())->method('ask')->willReturn($askResult); + + if ($answer === 'Y' || $answer === 'y') { + $this->encryptionManager->expects($this->once()) + ->method('getEncryptionModule')->willReturn($this->encryptionModule); + $this->encryptionModule->expects($this->once()) + ->method('encryptAll')->with($this->consoleInput, $this->consoleOutput); + } else { + $this->encryptionManager->expects($this->never())->method('getEncryptionModule'); + $this->encryptionModule->expects($this->never())->method('encryptAll'); + } + + $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public static function dataTestExecute(): array { + return [ + ['y', true], ['Y', true], ['n', false], ['N', false], ['', false] + ]; + } + + + public function testExecuteException(): void { + $this->expectException(\Exception::class); + + $command = new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper); + $this->encryptionManager->expects($this->once())->method('isEnabled')->willReturn(false); + $this->encryptionManager->expects($this->never())->method('getEncryptionModule'); + $this->encryptionModule->expects($this->never())->method('encryptAll'); + $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Encryption/SetDefaultModuleTest.php b/tests/Core/Command/Encryption/SetDefaultModuleTest.php new file mode 100644 index 00000000000..df38d730db3 --- /dev/null +++ b/tests/Core/Command/Encryption/SetDefaultModuleTest.php @@ -0,0 +1,130 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Encryption; + +use OC\Core\Command\Encryption\SetDefaultModule; +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class SetDefaultModuleTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject|IManager */ + protected $manager; + /** @var \PHPUnit\Framework\MockObject\MockObject|IConfig */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->manager = $this->getMockBuilder(IManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->config = $this->getMockBuilder(IConfig::class) + ->getMock(); + + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + $this->command = new SetDefaultModule($this->manager, $this->config); + } + + + public static function dataSetDefaultModule(): array { + return [ + ['ID0', 'ID0', null, null, 'already'], + ['ID0', 'ID1', 'ID1', true, 'info'], + ['ID0', 'ID1', 'ID1', false, 'error'], + ]; + } + + /** + * + * @param string $oldModule + * @param string $newModule + * @param string $updateModule + * @param bool $updateSuccess + * @param string $expectedString + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSetDefaultModule')] + public function testSetDefaultModule($oldModule, $newModule, $updateModule, $updateSuccess, $expectedString): void { + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('module') + ->willReturn($newModule); + + $this->manager->expects($this->once()) + ->method('getDefaultEncryptionModuleId') + ->willReturn($oldModule); + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('maintenance', false) + ->willReturn(false); + + if ($updateModule) { + $this->manager->expects($this->once()) + ->method('setDefaultEncryptionModule') + ->with($updateModule) + ->willReturn($updateSuccess); + } + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains($expectedString)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + /** + * + * @param string $oldModule + * @param string $newModule + * @param string $updateModule + * @param bool $updateSuccess + * @param string $expectedString + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSetDefaultModule')] + public function testMaintenanceMode($oldModule, $newModule, $updateModule, $updateSuccess, $expectedString): void { + $this->consoleInput->expects($this->never()) + ->method('getArgument') + ->with('module') + ->willReturn($newModule); + + $this->manager->expects($this->never()) + ->method('getDefaultEncryptionModuleId') + ->willReturn($oldModule); + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('maintenance', false) + ->willReturn(true); + + $calls = [ + 'Maintenance mode must be disabled when setting default module,', + 'in order to load the relevant encryption modules correctly.', + ]; + $this->consoleOutput->expects($this->exactly(2)) + ->method('writeln') + ->willReturnCallback(function ($message) use (&$calls): void { + $expected = array_shift($calls); + $this->assertStringContainsString($expected, $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Group/AddTest.php b/tests/Core/Command/Group/AddTest.php new file mode 100644 index 00000000000..24f2d823292 --- /dev/null +++ b/tests/Core/Command/Group/AddTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\Add; +use OCP\IGroup; +use OCP\IGroupManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class AddTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var Add */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->command = new Add($this->groupManager); + + $this->input = $this->createMock(InputInterface::class); + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) { + if ($arg === 'groupid') { + return 'myGroup'; + } + throw new \Exception(); + }); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testGroupExists(): void { + $gid = 'myGroup'; + $group = $this->createMock(IGroup::class); + $this->groupManager->method('get') + ->with($gid) + ->willReturn($group); + + $this->groupManager->expects($this->never()) + ->method('createGroup'); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('<error>Group "' . $gid . '" already exists.</error>')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testAdd(): void { + $gid = 'myGroup'; + $group = $this->createMock(IGroup::class); + $group->method('getGID') + ->willReturn($gid); + $this->groupManager->method('createGroup') + ->willReturn($group); + + $this->groupManager->expects($this->once()) + ->method('createGroup') + ->with($this->equalTo($gid)); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('Created group "' . $group->getGID() . '"')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Group/AddUserTest.php b/tests/Core/Command/Group/AddUserTest.php new file mode 100644 index 00000000000..68c8cecdba1 --- /dev/null +++ b/tests/Core/Command/Group/AddUserTest.php @@ -0,0 +1,101 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\AddUser; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class AddUserTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; + + /** @var AddUser */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->command = new AddUser($this->userManager, $this->groupManager); + + $this->input = $this->createMock(InputInterface::class); + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) { + if ($arg === 'group') { + return 'myGroup'; + } elseif ($arg === 'user') { + return 'myUser'; + } + throw new \Exception(); + }); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testNoGroup(): void { + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn(null); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<error>group not found</error>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testNoUser(): void { + $group = $this->createMock(IGroup::class); + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn($group); + + $this->userManager->method('get') + ->with('myUser') + ->willReturn(null); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<error>user not found</error>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testAdd(): void { + $group = $this->createMock(IGroup::class); + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn($group); + + $user = $this->createMock(IUser::class); + $this->userManager->method('get') + ->with('myUser') + ->willReturn($user); + + $group->expects($this->once()) + ->method('addUser') + ->with($user); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Group/DeleteTest.php b/tests/Core/Command/Group/DeleteTest.php new file mode 100644 index 00000000000..289c6a7c322 --- /dev/null +++ b/tests/Core/Command/Group/DeleteTest.php @@ -0,0 +1,132 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\Delete; +use OCP\IGroup; +use OCP\IGroupManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var Delete */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->command = new Delete($this->groupManager); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testDoesNotExists(): void { + $gid = 'myGroup'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + $this->groupManager->method('groupExists') + ->with($gid) + ->willReturn(false); + + $this->groupManager->expects($this->never()) + ->method('get'); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('<error>Group "' . $gid . '" does not exist.</error>')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testDeleteAdmin(): void { + $gid = 'admin'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + + $this->groupManager->expects($this->never()) + ->method($this->anything()); + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('<error>Group "' . $gid . '" could not be deleted.</error>')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testDeleteFailed(): void { + $gid = 'myGroup'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + $group = $this->createMock(IGroup::class); + $group->method('delete') + ->willReturn(false); + $this->groupManager->method('groupExists') + ->with($gid) + ->willReturn(true); + $this->groupManager->method('get') + ->with($gid) + ->willReturn($group); + + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('<error>Group "' . $gid . '" could not be deleted. Please check the logs.</error>')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testDelete(): void { + $gid = 'myGroup'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + $group = $this->createMock(IGroup::class); + $group->method('delete') + ->willReturn(true); + $this->groupManager->method('groupExists') + ->with($gid) + ->willReturn(true); + $this->groupManager->method('get') + ->with($gid) + ->willReturn($group); + + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('Group "' . $gid . '" was removed')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Group/InfoTest.php b/tests/Core/Command/Group/InfoTest.php new file mode 100644 index 00000000000..87f59d2adc4 --- /dev/null +++ b/tests/Core/Command/Group/InfoTest.php @@ -0,0 +1,98 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\Info; +use OCP\IGroup; +use OCP\IGroupManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class InfoTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var Info|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->command = $this->getMockBuilder(Info::class) + ->setConstructorArgs([$this->groupManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testDoesNotExists(): void { + $gid = 'myGroup'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + $this->groupManager->method('get') + ->with($gid) + ->willReturn(null); + + $this->output->expects($this->once()) + ->method('writeln') + ->with($this->equalTo('<error>Group "' . $gid . '" does not exist.</error>')); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testInfo(): void { + $gid = 'myGroup'; + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($gid) { + if ($arg === 'groupid') { + return $gid; + } + throw new \Exception(); + }); + + $group = $this->createMock(IGroup::class); + $group->method('getGID')->willReturn($gid); + $group->method('getDisplayName') + ->willReturn('My Group'); + $group->method('getBackendNames') + ->willReturn(['Database']); + + $this->groupManager->method('get') + ->with($gid) + ->willReturn($group); + + $this->command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with( + $this->equalTo($this->input), + $this->equalTo($this->output), + [ + 'groupID' => 'myGroup', + 'displayName' => 'My Group', + 'backends' => ['Database'], + ] + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Group/ListCommandTest.php b/tests/Core/Command/Group/ListCommandTest.php new file mode 100644 index 00000000000..aaca772d714 --- /dev/null +++ b/tests/Core/Command/Group/ListCommandTest.php @@ -0,0 +1,204 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\ListCommand; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ListCommandTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var ListCommand|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->command = $this->getMockBuilder(ListCommand::class) + ->setConstructorArgs([$this->groupManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testExecute(): void { + $group1 = $this->createMock(IGroup::class); + $group1->method('getGID')->willReturn('group1'); + $group2 = $this->createMock(IGroup::class); + $group2->method('getGID')->willReturn('group2'); + $group3 = $this->createMock(IGroup::class); + $group3->method('getGID')->willReturn('group3'); + + $user = $this->createMock(IUser::class); + + $this->groupManager->method('search') + ->with( + '', + 100, + 42, + )->willReturn([$group1, $group2, $group3]); + + $group1->method('getUsers') + ->willReturn([ + 'user1' => $user, + 'user2' => $user, + ]); + + $group2->method('getUsers') + ->willReturn([ + ]); + + $group3->method('getUsers') + ->willReturn([ + 'user1' => $user, + 'user3' => $user, + ]); + + $this->input->method('getOption') + ->willReturnCallback(function ($arg) { + if ($arg === 'limit') { + return '100'; + } elseif ($arg === 'offset') { + return '42'; + } elseif ($arg === 'info') { + return null; + } + throw new \Exception(); + }); + + $this->command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with( + $this->equalTo($this->input), + $this->equalTo($this->output), + $this->callback( + fn ($iterator) => iterator_to_array($iterator) === [ + 'group1' => [ + 'user1', + 'user2', + ], + 'group2' => [ + ], + 'group3' => [ + 'user1', + 'user3', + ] + ] + ) + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testInfo(): void { + $group1 = $this->createMock(IGroup::class); + $group1->method('getGID')->willReturn('group1'); + $group1->method('getDisplayName')->willReturn('Group 1'); + $group2 = $this->createMock(IGroup::class); + $group2->method('getGID')->willReturn('group2'); + $group2->method('getDisplayName')->willReturn('Group 2'); + $group3 = $this->createMock(IGroup::class); + $group3->method('getGID')->willReturn('group3'); + $group3->method('getDisplayName')->willReturn('Group 3'); + + $user = $this->createMock(IUser::class); + + $this->groupManager->method('search') + ->with( + '', + 100, + 42, + )->willReturn([$group1, $group2, $group3]); + + $group1->method('getUsers') + ->willReturn([ + 'user1' => $user, + 'user2' => $user, + ]); + + $group1->method('getBackendNames') + ->willReturn(['Database']); + + $group2->method('getUsers') + ->willReturn([ + ]); + + $group2->method('getBackendNames') + ->willReturn(['Database']); + + $group3->method('getUsers') + ->willReturn([ + 'user1' => $user, + 'user3' => $user, + ]); + + $group3->method('getBackendNames') + ->willReturn(['LDAP']); + + $this->input->method('getOption') + ->willReturnCallback(function ($arg) { + if ($arg === 'limit') { + return '100'; + } elseif ($arg === 'offset') { + return '42'; + } elseif ($arg === 'info') { + return true; + } + throw new \Exception(); + }); + + $this->command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with( + $this->equalTo($this->input), + $this->equalTo($this->output), + $this->callback( + fn ($iterator) => iterator_to_array($iterator) === [ + 'group1' => [ + 'displayName' => 'Group 1', + 'backends' => ['Database'], + 'users' => [ + 'user1', + 'user2', + ], + ], + 'group2' => [ + 'displayName' => 'Group 2', + 'backends' => ['Database'], + 'users' => [], + ], + 'group3' => [ + 'displayName' => 'Group 3', + 'backends' => ['LDAP'], + 'users' => [ + 'user1', + 'user3', + ], + ] + ] + ) + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Group/RemoveUserTest.php b/tests/Core/Command/Group/RemoveUserTest.php new file mode 100644 index 00000000000..74343e77d3f --- /dev/null +++ b/tests/Core/Command/Group/RemoveUserTest.php @@ -0,0 +1,101 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\Group; + +use OC\Core\Command\Group\RemoveUser; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class RemoveUserTest extends TestCase { + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; + + /** @var RemoveUser */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->groupManager = $this->createMock(IGroupManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->command = new RemoveUser($this->userManager, $this->groupManager); + + $this->input = $this->createMock(InputInterface::class); + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) { + if ($arg === 'group') { + return 'myGroup'; + } elseif ($arg === 'user') { + return 'myUser'; + } + throw new \Exception(); + }); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testNoGroup(): void { + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn(null); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<error>group not found</error>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testNoUser(): void { + $group = $this->createMock(IGroup::class); + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn($group); + + $this->userManager->method('get') + ->with('myUser') + ->willReturn(null); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<error>user not found</error>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testAdd(): void { + $group = $this->createMock(IGroup::class); + $this->groupManager->method('get') + ->with('myGroup') + ->willReturn($group); + + $user = $this->createMock(IUser::class); + $this->userManager->method('get') + ->with('myUser') + ->willReturn($user); + + $group->expects($this->once()) + ->method('removeUser') + ->with($user); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/Log/FileTest.php b/tests/Core/Command/Log/FileTest.php new file mode 100644 index 00000000000..1aaf398b875 --- /dev/null +++ b/tests/Core/Command/Log/FileTest.php @@ -0,0 +1,112 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Log; + +use OC\Core\Command\Log\File; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class FileTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + $this->command = new File($config); + } + + public function testEnable(): void { + $this->config->method('getSystemValue')->willReturnArgument(1); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['enable', 'true'] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_type', 'file'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeFile(): void { + $this->config->method('getSystemValue')->willReturnArgument(1); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['file', '/foo/bar/file.log'] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('logfile', '/foo/bar/file.log'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public static function changeRotateSizeProvider(): array { + return [ + ['42', 42], + ['0', 0], + ['1 kB', 1024], + ['5MB', 5 * 1024 * 1024], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('changeRotateSizeProvider')] + public function testChangeRotateSize($optionValue, $configValue): void { + $this->config->method('getSystemValue')->willReturnArgument(1); + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['rotate-size', $optionValue] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_rotate_size', $configValue); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testGetConfiguration(): void { + $this->config->method('getSystemValue') + ->willReturnMap([ + ['log_type', 'file', 'log_type_value'], + ['datadirectory', \OC::$SERVERROOT . '/data', '/data/directory/'], + ['logfile', '/data/directory/nextcloud.log', '/var/log/nextcloud.log'], + ['log_rotate_size', 100 * 1024 * 1024, 5 * 1024 * 1024], + ]); + + $calls = [ + ['Log backend file: disabled'], + ['Log file: /var/log/nextcloud.log'], + ['Rotate at: 5 MB'], + ]; + $this->consoleOutput->expects($this->exactly(3)) + ->method('writeln') + ->willReturnCallback(function (string $message) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected[0], $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Log/ManageTest.php b/tests/Core/Command/Log/ManageTest.php new file mode 100644 index 00000000000..8b307048719 --- /dev/null +++ b/tests/Core/Command/Log/ManageTest.php @@ -0,0 +1,164 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Log; + +use OC\Core\Command\Log\Manage; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ManageTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + $this->command = new Manage($config); + } + + public function testChangeBackend(): void { + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['backend', 'syslog'] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_type', 'syslog'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeLevel(): void { + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['level', 'debug'] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('loglevel', 0); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeTimezone(): void { + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['timezone', 'UTC'] + ]); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('logtimezone', 'UTC'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + + public function testValidateBackend(): void { + $this->expectException(\InvalidArgumentException::class); + + self::invokePrivate($this->command, 'validateBackend', ['notabackend']); + } + + + public function testValidateTimezone(): void { + $this->expectException(\Exception::class); + + // this might need to be changed when humanity colonises Mars + self::invokePrivate($this->command, 'validateTimezone', ['Mars/OlympusMons']); + } + + public static function dataConvertLevelString(): array { + return [ + ['dEbug', 0], + ['inFO', 1], + ['Warning', 2], + ['wArn', 2], + ['error', 3], + ['eRr', 3], + ['fAtAl', 4], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataConvertLevelString')] + public function testConvertLevelString(string $levelString, int $expectedInt): void { + $this->assertEquals($expectedInt, + self::invokePrivate($this->command, 'convertLevelString', [$levelString]) + ); + } + + + public function testConvertLevelStringInvalid(): void { + $this->expectException(\InvalidArgumentException::class); + + self::invokePrivate($this->command, 'convertLevelString', ['abc']); + } + + public static function dataConvertLevelNumber(): array { + return [ + [0, 'Debug'], + [1, 'Info'], + [2, 'Warning'], + [3, 'Error'], + [4, 'Fatal'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataConvertLevelNumber')] + public function testConvertLevelNumber(int $levelNum, string $expectedString): void { + $this->assertEquals($expectedString, + self::invokePrivate($this->command, 'convertLevelNumber', [$levelNum]) + ); + } + + + public function testConvertLevelNumberInvalid(): void { + $this->expectException(\InvalidArgumentException::class); + + self::invokePrivate($this->command, 'convertLevelNumber', [11]); + } + + public function testGetConfiguration(): void { + $this->config->expects($this->exactly(3)) + ->method('getSystemValue') + ->willReturnMap([ + ['log_type', 'file', 'log_type_value'], + ['loglevel', 2, 0], + ['logtimezone', 'UTC', 'logtimezone_value'], + ]); + + $calls = [ + ['Enabled logging backend: log_type_value'], + ['Log level: Debug (0)'], + ['Log timezone: logtimezone_value'], + ]; + $this->consoleOutput->expects($this->exactly(3)) + ->method('writeln') + ->willReturnCallback(function (string $message) use (&$calls): void { + $call = array_shift($calls); + $this->assertStringContainsString($call[0], $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Maintenance/DataFingerprintTest.php b/tests/Core/Command/Maintenance/DataFingerprintTest.php new file mode 100644 index 00000000000..99004a7a5f5 --- /dev/null +++ b/tests/Core/Command/Maintenance/DataFingerprintTest.php @@ -0,0 +1,53 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Maintenance; + +use OC\Core\Command\Maintenance\DataFingerprint; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DataFingerprintTest extends TestCase { + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $timeFactory; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->getMockBuilder(IConfig::class)->getMock(); + $this->timeFactory = $this->getMockBuilder(ITimeFactory::class)->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var IConfig $config */ + $this->command = new DataFingerprint($this->config, $this->timeFactory); + } + + public function testSetFingerPrint(): void { + $this->timeFactory->expects($this->once()) + ->method('getTime') + ->willReturn(42); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('data-fingerprint', md5(42)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php b/tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php new file mode 100644 index 00000000000..b85dcf87bbc --- /dev/null +++ b/tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php @@ -0,0 +1,174 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\Maintenance\Mimetype; + +use OC\Core\Command\Maintenance\Mimetype\UpdateDB; +use OC\Files\Type\Detection; +use OC\Files\Type\Loader; +use OCP\Files\IMimeTypeDetector; +use OCP\Files\IMimeTypeLoader; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class UpdateDBTest extends TestCase { + /** @var IMimeTypeDetector */ + protected $detector; + /** @var IMimeTypeLoader */ + protected $loader; + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->detector = $this->createMock(Detection::class); + $this->loader = $this->createMock(Loader::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new UpdateDB($this->detector, $this->loader); + } + + public function testNoop(): void { + $this->consoleInput->method('getOption') + ->with('repair-filecache') + ->willReturn(false); + + $this->detector->expects($this->once()) + ->method('getAllMappings') + ->willReturn([ + 'ext' => ['testing/existingmimetype'] + ]); + $this->loader->expects($this->once()) + ->method('exists') + ->with('testing/existingmimetype') + ->willReturn(true); + + $this->loader->expects($this->never()) + ->method('updateFilecache'); + + $calls = [ + 'Added 0 new mimetypes', + 'Updated 0 filecache rows', + ]; + $this->consoleOutput->expects($this->exactly(2)) + ->method('writeln') + ->willReturnCallback(function ($message) use (&$calls): void { + $expected = array_shift($calls); + $this->assertStringContainsString($expected, $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testAddMimetype(): void { + $this->consoleInput->method('getOption') + ->with('repair-filecache') + ->willReturn(false); + + $this->detector->expects($this->once()) + ->method('getAllMappings') + ->willReturn([ + 'ext' => ['testing/existingmimetype'], + 'new' => ['testing/newmimetype'] + ]); + $this->loader->expects($this->exactly(2)) + ->method('exists') + ->willReturnMap([ + ['testing/existingmimetype', true], + ['testing/newmimetype', false], + ]); + $this->loader->expects($this->exactly(2)) + ->method('getId') + ->willReturnMap([ + ['testing/existingmimetype', 1], + ['testing/newmimetype', 2], + ]); + + $this->loader->expects($this->once()) + ->method('updateFilecache') + ->with('new', 2) + ->willReturn(3); + + $calls = [ + 'Added mimetype "testing/newmimetype" to database', + 'Updated 3 filecache rows for mimetype "testing/newmimetype"', + 'Added 1 new mimetypes', + 'Updated 3 filecache rows', + ]; + $this->consoleOutput->expects($this->exactly(4)) + ->method('writeln') + ->willReturnCallback(function ($message) use (&$calls): void { + $expected = array_shift($calls); + $this->assertStringContainsString($expected, $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testSkipComments(): void { + $this->detector->expects($this->once()) + ->method('getAllMappings') + ->willReturn([ + '_comment' => 'some comment in the JSON' + ]); + $this->loader->expects($this->never()) + ->method('exists'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testRepairFilecache(): void { + $this->consoleInput->method('getOption') + ->with('repair-filecache') + ->willReturn(true); + + $this->detector->expects($this->once()) + ->method('getAllMappings') + ->willReturn([ + 'ext' => ['testing/existingmimetype'], + ]); + $this->loader->expects($this->exactly(1)) + ->method('exists') + ->willReturnMap([ + ['testing/existingmimetype', true], + ]); + $this->loader->expects($this->exactly(1)) + ->method('getId') + ->willReturnMap([ + ['testing/existingmimetype', 1], + ]); + + $this->loader->expects($this->once()) + ->method('updateFilecache') + ->with('ext', 1) + ->willReturn(3); + + $calls = [ + 'Updated 3 filecache rows for mimetype "testing/existingmimetype"', + 'Added 0 new mimetypes', + 'Updated 3 filecache rows', + ]; + $this->consoleOutput->expects($this->exactly(3)) + ->method('writeln') + ->willReturnCallback(function ($message) use (&$calls): void { + $expected = array_shift($calls); + $this->assertStringContainsString($expected, $message); + }); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Maintenance/ModeTest.php b/tests/Core/Command/Maintenance/ModeTest.php new file mode 100644 index 00000000000..5a9a90b0197 --- /dev/null +++ b/tests/Core/Command/Maintenance/ModeTest.php @@ -0,0 +1,151 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Tests\Core\Command\Maintenance; + +use OC\Core\Command\Maintenance\Mode; +use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +/** + * This class provides tests methods for the Mode command. + * + * @package Tests\Core\Command\Maintenance + */ +class ModeTest extends TestCase { + /** + * A config mock passed to the command. + * + * @var IConfig|MockObject + */ + private $config; + + /** + * Holds a Mode command instance with a config mock. + * + * @var Mode + */ + private $mode; + + /** + * An input mock for tests. + * + * @var InputInterface|MockObject + */ + private $input; + + /** + * An output mock for tests. + * + * @var OutputInterface|MockObject + */ + private $output; + + /** + * Setups the test environment. + * + * @return void + */ + protected function setUp(): void { + parent::setUp(); + $this->config = $this->getMockBuilder(IConfig::class) + ->getMock(); + $this->mode = new Mode($this->config); + $this->input = $this->getMockBuilder(InputInterface::class) + ->getMock(); + $this->output = $this->getMockBuilder(OutputInterface::class) + ->getMock(); + } + + /** + * Provides test data for the execute test. + * + * @return array + */ + public static function getExecuteTestData(): array { + return [ + 'off -> on' => [ + 'on', // command option + false, // current maintenance mode state + true, // expected maintenance mode state, null for no change + 'Maintenance mode enabled', // expected output + ], + 'on -> off' => [ + 'off', + true, + false, + 'Maintenance mode disabled', + ], + 'on -> on' => [ + 'on', + true, + null, + 'Maintenance mode already enabled', + ], + 'off -> off' => [ + 'off', + false, + null, + 'Maintenance mode already disabled', + ], + 'no option, maintenance enabled' => [ + '', + true, + null, + 'Maintenance mode is currently enabled', + ], + 'no option, maintenance disabled' => [ + '', + false, + null, + 'Maintenance mode is currently disabled', + ], + ]; + } + + /** + * Asserts that execute works as expected. + * + * @param string $option The command option. + * @param bool $currentMaintenanceState The current maintenance state. + * @param null|bool $expectedMaintenanceState + * The expected maintenance state. Null for no change. + * @param string $expectedOutput The expected command output. + * @throws \Exception + */ + #[\PHPUnit\Framework\Attributes\DataProvider('getExecuteTestData')] + public function testExecute( + string $option, + bool $currentMaintenanceState, + $expectedMaintenanceState, + string $expectedOutput, + ): void { + $this->config->expects($this->any()) + ->method('getSystemValueBool') + ->willReturn($currentMaintenanceState); + + if ($expectedMaintenanceState !== null) { + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('maintenance', $expectedMaintenanceState); + } + + $this->input->expects($this->any()) + ->method('getOption') + ->willReturnCallback(function ($callOption) use ($option) { + return $callOption === $option; + }); + + $this->output->expects($this->once()) + ->method('writeln') + ->with($expectedOutput); + + $this->mode->run($this->input, $this->output); + } +} diff --git a/tests/Core/Command/Maintenance/UpdateTheme.php b/tests/Core/Command/Maintenance/UpdateTheme.php new file mode 100644 index 00000000000..9c9a2b903a7 --- /dev/null +++ b/tests/Core/Command/Maintenance/UpdateTheme.php @@ -0,0 +1,63 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\Maintenance; + +use OC\Core\Command\Maintenance\UpdateTheme; +use OC\Files\Type\Detection; +use OCP\Files\IMimeTypeDetector; +use OCP\ICache; +use OCP\ICacheFactory; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class UpdateThemeTest extends TestCase { + /** @var IMimeTypeDetector */ + protected $detector; + /** @var ICacheFactory */ + protected $cacheFactory; + + + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->detector = $this->createMock(Detection::class); + $this->cacheFactory = $this->createMock(ICacheFactory::class); + + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + $this->command = new UpdateTheme($this->detector, $this->cacheFactory); + } + + public function testThemeUpdate(): void { + $this->consoleInput->method('getOption') + ->with('maintenance:theme:update') + ->willReturn(true); + $this->detector->expects($this->once()) + ->method('getAllAliases') + ->willReturn([]); + $cache = $this->createMock(ICache::class); + $cache->expects($this->once()) + ->method('clear') + ->with(''); + $this->cacheFactory->expects($this->once()) + ->method('createDistributed') + ->with('imagePath') + ->willReturn($cache); + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/Preview/CleanupTest.php b/tests/Core/Command/Preview/CleanupTest.php new file mode 100644 index 00000000000..e4a83246e5b --- /dev/null +++ b/tests/Core/Command/Preview/CleanupTest.php @@ -0,0 +1,175 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Core\Command\Preview; + +use OC\Core\Command\Preview\Cleanup; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class CleanupTest extends TestCase { + private IRootFolder&MockObject $rootFolder; + private LoggerInterface&MockObject $logger; + private InputInterface&MockObject $input; + private OutputInterface&MockObject $output; + private Cleanup $repair; + + protected function setUp(): void { + parent::setUp(); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->repair = new Cleanup( + $this->rootFolder, + $this->logger, + ); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testCleanup(): void { + $previewFolder = $this->createMock(Folder::class); + $previewFolder->expects($this->once()) + ->method('isDeletable') + ->willReturn(true); + + $previewFolder->expects($this->once()) + ->method('delete'); + + $appDataFolder = $this->createMock(Folder::class); + $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder); + $appDataFolder->expects($this->once())->method('newFolder')->with('preview'); + + $this->rootFolder->expects($this->once()) + ->method('getAppDataDirectoryName') + ->willReturn('appdata_some_id'); + + $this->rootFolder->expects($this->once()) + ->method('get') + ->with('appdata_some_id') + ->willReturn($appDataFolder); + + $this->output->expects($this->exactly(3))->method('writeln') + ->with(self::callback(function (string $message): bool { + static $i = 0; + return match (++$i) { + 1 => $message === 'Preview folder deleted', + 2 => $message === 'Preview folder recreated', + 3 => $message === 'Previews removed' + }; + })); + + $this->assertEquals(0, $this->repair->run($this->input, $this->output)); + } + + public function testCleanupWhenNotDeletable(): void { + $previewFolder = $this->createMock(Folder::class); + $previewFolder->expects($this->once()) + ->method('isDeletable') + ->willReturn(false); + + $previewFolder->expects($this->never()) + ->method('delete'); + + $appDataFolder = $this->createMock(Folder::class); + $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder); + $appDataFolder->expects($this->never())->method('newFolder')->with('preview'); + + $this->rootFolder->expects($this->once()) + ->method('getAppDataDirectoryName') + ->willReturn('appdata_some_id'); + + $this->rootFolder->expects($this->once()) + ->method('get') + ->with('appdata_some_id') + ->willReturn($appDataFolder); + + $this->logger->expects($this->once())->method('error')->with("Previews can't be removed: preview folder isn't deletable"); + $this->output->expects($this->once())->method('writeln')->with("Previews can't be removed: preview folder isn't deletable"); + + $this->assertEquals(1, $this->repair->run($this->input, $this->output)); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataForTestCleanupWithDeleteException')] + public function testCleanupWithDeleteException(string $exceptionClass, string $errorMessage): void { + $previewFolder = $this->createMock(Folder::class); + $previewFolder->expects($this->once()) + ->method('isDeletable') + ->willReturn(true); + + $previewFolder->expects($this->once()) + ->method('delete') + ->willThrowException(new $exceptionClass()); + + $appDataFolder = $this->createMock(Folder::class); + $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder); + $appDataFolder->expects($this->never())->method('newFolder')->with('preview'); + + $this->rootFolder->expects($this->once()) + ->method('getAppDataDirectoryName') + ->willReturn('appdata_some_id'); + + $this->rootFolder->expects($this->once()) + ->method('get') + ->with('appdata_some_id') + ->willReturn($appDataFolder); + + $this->logger->expects($this->once())->method('error')->with($errorMessage); + $this->output->expects($this->once())->method('writeln')->with($errorMessage); + + $this->assertEquals(1, $this->repair->run($this->input, $this->output)); + } + + public static function dataForTestCleanupWithDeleteException(): array { + return [ + [NotFoundException::class, "Previews weren't deleted: preview folder was not found while deleting it"], + [NotPermittedException::class, "Previews weren't deleted: you don't have the permission to delete preview folder"], + ]; + } + + public function testCleanupWithCreateException(): void { + $previewFolder = $this->createMock(Folder::class); + $previewFolder->expects($this->once()) + ->method('isDeletable') + ->willReturn(true); + + $previewFolder->expects($this->once()) + ->method('delete'); + + $appDataFolder = $this->createMock(Folder::class); + $appDataFolder->expects($this->once())->method('get')->with('preview')->willReturn($previewFolder); + $appDataFolder->expects($this->once())->method('newFolder')->with('preview')->willThrowException(new NotPermittedException()); + + $this->rootFolder->expects($this->once()) + ->method('getAppDataDirectoryName') + ->willReturn('appdata_some_id'); + + $this->rootFolder->expects($this->once()) + ->method('get') + ->with('appdata_some_id') + ->willReturn($appDataFolder); + + $this->output->expects($this->exactly(2))->method('writeln') + ->with(self::callback(function (string $message): bool { + static $i = 0; + return match (++$i) { + 1 => $message === 'Preview folder deleted', + 2 => $message === "Preview folder was deleted, but you don't have the permission to create preview folder", + }; + })); + + $this->logger->expects($this->once())->method('error')->with("Preview folder was deleted, but you don't have the permission to create preview folder"); + + $this->assertEquals(1, $this->repair->run($this->input, $this->output)); + } +} diff --git a/tests/Core/Command/Preview/RepairTest.php b/tests/Core/Command/Preview/RepairTest.php new file mode 100644 index 00000000000..9b9cde6dd95 --- /dev/null +++ b/tests/Core/Command/Preview/RepairTest.php @@ -0,0 +1,153 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Tests\Core\Command\Preview; + +use bantu\IniGetWrapper\IniGetWrapper; +use OC\Core\Command\Preview\Repair; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\IConfig; +use OCP\Lock\ILockingProvider; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class RepairTest extends TestCase { + /** @var IConfig|MockObject */ + private $config; + /** @var IRootFolder|MockObject */ + private $rootFolder; + /** @var LoggerInterface|MockObject */ + private $logger; + /** @var IniGetWrapper|MockObject */ + private $iniGetWrapper; + /** @var InputInterface|MockObject */ + private $input; + /** @var OutputInterface|MockObject */ + private $output; + /** @var string */ + private $outputLines = ''; + /** @var Repair */ + private $repair; + + protected function setUp(): void { + parent::setUp(); + $this->config = $this->getMockBuilder(IConfig::class) + ->getMock(); + $this->rootFolder = $this->getMockBuilder(IRootFolder::class) + ->getMock(); + $this->logger = $this->getMockBuilder(LoggerInterface::class) + ->getMock(); + $this->iniGetWrapper = $this->getMockBuilder(IniGetWrapper::class) + ->getMock(); + $this->repair = new Repair( + $this->config, + $this->rootFolder, + $this->logger, + $this->iniGetWrapper, + $this->createMock(ILockingProvider::class) + ); + $this->input = $this->createMock(InputInterface::class); + $this->input->expects($this->any()) + ->method('getOption') + ->willReturnCallback(function ($parameter) { + if ($parameter === 'batch') { + return true; + } + return null; + }); + $this->output = $this->getMockBuilder(ConsoleOutput::class) + ->onlyMethods(['section', 'writeln', 'getFormatter']) + ->getMock(); + $self = $this; + + /* We need format method to return a string */ + $outputFormatter = $this->createMock(OutputFormatterInterface::class); + $outputFormatter->method('isDecorated')->willReturn(false); + $outputFormatter->method('format')->willReturnArgument(0); + + $this->output->expects($this->any()) + ->method('getFormatter') + ->willReturn($outputFormatter); + $this->output->expects($this->any()) + ->method('writeln') + ->willReturnCallback(function ($line) use ($self): void { + $self->outputLines .= $line . "\n"; + }); + } + + public static function dataEmptyTest(): array { + /** directoryNames, expectedOutput */ + return [ + [ + [], + 'All previews are already migrated.' + ], + [ + [['name' => 'a'], ['name' => 'b'], ['name' => 'c']], + 'All previews are already migrated.' + ], + [ + [['name' => '0', 'content' => ['folder', 'folder']], ['name' => 'b'], ['name' => 'c']], + 'All previews are already migrated.' + ], + [ + [['name' => '0', 'content' => ['file', 'folder', 'folder']], ['name' => 'b'], ['name' => 'c']], + 'A total of 1 preview files need to be migrated.' + ], + [ + [['name' => '23'], ['name' => 'b'], ['name' => 'c']], + 'A total of 1 preview files need to be migrated.' + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataEmptyTest')] + public function testEmptyExecute($directoryNames, $expectedOutput): void { + $previewFolder = $this->getMockBuilder(Folder::class) + ->getMock(); + $directories = array_map(function ($element) { + $dir = $this->getMockBuilder(Folder::class) + ->getMock(); + $dir->expects($this->any()) + ->method('getName') + ->willReturn($element['name']); + if (isset($element['content'])) { + $list = []; + foreach ($element['content'] as $item) { + if ($item === 'file') { + $list[] = $this->getMockBuilder(Node::class) + ->getMock(); + } elseif ($item === 'folder') { + $list[] = $this->getMockBuilder(Folder::class) + ->getMock(); + } + } + $dir->expects($this->once()) + ->method('getDirectoryListing') + ->willReturn($list); + } + return $dir; + }, $directoryNames); + $previewFolder->expects($this->once()) + ->method('getDirectoryListing') + ->willReturn($directories); + $this->rootFolder->expects($this->once()) + ->method('get') + ->with('appdata_/preview') + ->willReturn($previewFolder); + + $this->repair->run($this->input, $this->output); + + $this->assertStringContainsString($expectedOutput, $this->outputLines); + } +} diff --git a/tests/Core/Command/SystemTag/AddTest.php b/tests/Core/Command/SystemTag/AddTest.php new file mode 100644 index 00000000000..5f3c7174758 --- /dev/null +++ b/tests/Core/Command/SystemTag/AddTest.php @@ -0,0 +1,121 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\SystemTag; + +use OC\Core\Command\SystemTag\Add; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\TagAlreadyExistsException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class AddTest extends TestCase { + /** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */ + private $systemTagManager; + + /** @var ListCommand|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->systemTagManager = $this->createMock(ISystemTagManager::class); + $this->command = $this->getMockBuilder(Add::class) + ->setConstructorArgs([$this->systemTagManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testExecute(): void { + $tagId = '42'; + $tagName = 'wichtig'; + $tagAccess = 'public'; + + $tag = $this->createMock(ISystemTag::class); + $tag->method('getId')->willReturn($tagId); + $tag->method('getName')->willReturn($tagName); + $tag->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_PUBLIC); + + $this->systemTagManager->method('createTag') + ->with( + $tagName, + true, + true + )->willReturn($tag); + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagName, $tagAccess) { + if ($arg === 'name') { + return $tagName; + } elseif ($arg === 'access') { + return $tagAccess; + } + throw new \Exception(); + }); + + $this->command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with( + $this->equalTo($this->input), + $this->equalTo($this->output), + [ + 'id' => $tagId, + 'name' => $tagName, + 'access' => $tagAccess, + ] + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testAlreadyExists(): void { + $tagId = '42'; + $tagName = 'wichtig'; + $tagAccess = 'public'; + + $tag = $this->createMock(ISystemTag::class); + $tag->method('getId')->willReturn($tagId); + $tag->method('getName')->willReturn($tagName); + $tag->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_PUBLIC); + + $this->systemTagManager->method('createTag') + ->willReturnCallback(function ($tagName, $userVisible, $userAssignable): void { + throw new TagAlreadyExistsException( + 'Tag ("' . $tagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists' + ); + }); + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagName, $tagAccess) { + if ($arg === 'name') { + return $tagName; + } elseif ($arg === 'access') { + return $tagAccess; + } + throw new \Exception(); + }); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + '<error>Tag ("wichtig", 1, 1) already exists</error>' + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/SystemTag/DeleteTest.php b/tests/Core/Command/SystemTag/DeleteTest.php new file mode 100644 index 00000000000..bf756311000 --- /dev/null +++ b/tests/Core/Command/SystemTag/DeleteTest.php @@ -0,0 +1,83 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\SystemTag; + +use OC\Core\Command\SystemTag\Delete; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteTest extends TestCase { + /** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */ + private $systemTagManager; + + /** @var ListCommand|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->systemTagManager = $this->createMock(ISystemTagManager::class); + $this->command = $this->getMockBuilder(Delete::class) + ->setConstructorArgs([$this->systemTagManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testExecute(): void { + $tagId = 69; + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagId) { + if ($arg === 'id') { + return $tagId; + } + throw new \Exception(); + }); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<info>The specified tag was deleted</info>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testNotFound(): void { + $tagId = 69; + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagId) { + if ($arg === 'id') { + return $tagId; + } + throw new \Exception(); + }); + + $this->systemTagManager->method('deleteTags') + ->willReturnCallback(function ($tagId): void { + throw new TagNotFoundException(); + }); + + $this->output->expects($this->once()) + ->method('writeln') + ->with('<error>Tag not found</error>'); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/SystemTag/EditTest.php b/tests/Core/Command/SystemTag/EditTest.php new file mode 100644 index 00000000000..b22151e3608 --- /dev/null +++ b/tests/Core/Command/SystemTag/EditTest.php @@ -0,0 +1,186 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\SystemTag; + +use OC\Core\Command\SystemTag\Edit; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\TagAlreadyExistsException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class EditTest extends TestCase { + /** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */ + private $systemTagManager; + + /** @var ListCommand|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->systemTagManager = $this->createMock(ISystemTagManager::class); + $this->command = $this->getMockBuilder(Edit::class) + ->setConstructorArgs([$this->systemTagManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testExecute(): void { + $tagId = '5'; + $tagName = 'unwichtige Dateien'; + $newTagName = 'moderat wichtige Dateien'; + $newTagAccess = 'restricted'; + $newTagUserVisible = true; + $newTagUserAssignable = false; + + $tag = $this->createMock(ISystemTag::class); + $tag->method('getId')->willReturn($tagId); + $tag->method('getName')->willReturn($tagName); + $tag->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_INVISIBLE); + + $this->systemTagManager->method('getTagsByIds') + ->with($tagId) + ->willReturn([$tag]); + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagId) { + if ($arg === 'id') { + return $tagId; + } + throw new \Exception(); + }); + + $this->input->method('getOption') + ->willReturnCallback(function ($arg) use ($newTagName, $newTagAccess) { + if ($arg === 'name') { + return $newTagName; + } elseif ($arg === 'access') { + return $newTagAccess; + } + throw new \Exception(); + }); + + $this->systemTagManager->expects($this->once()) + ->method('updateTag') + ->with( + $tagId, + $newTagName, + $newTagUserVisible, + $newTagUserAssignable, + '' + ); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + '<info>Tag updated ("' . $newTagName . '", ' . json_encode($newTagUserVisible) . ', ' . json_encode($newTagUserAssignable) . ', "")</info>' + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testAlreadyExists(): void { + $tagId = '5'; + $tagName = 'unwichtige Dateien'; + $tagUserVisible = false; + $tagUserAssignable = false; + $newTagName = 'moderat wichtige Dateien'; + $newTagAccess = 'restricted'; + $newTagUserVisible = true; + $newTagUserAssignable = false; + + $tag = $this->createMock(ISystemTag::class); + $tag->method('getId')->willReturn($tagId); + $tag->method('getName')->willReturn($tagName); + $tag->method('isUserVisible')->willReturn($tagUserVisible); + $tag->method('isUserAssignable')->willReturn($tagUserAssignable); + $tag->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_INVISIBLE); + + $this->systemTagManager->method('getTagsByIds') + ->with($tagId) + ->willReturn([$tag]); + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagId) { + if ($arg === 'id') { + return $tagId; + } + throw new \Exception(); + }); + + $this->input->method('getOption') + ->willReturnCallback(function ($arg) use ($newTagName, $newTagAccess) { + if ($arg === 'name') { + return $newTagName; + } elseif ($arg === 'access') { + return $newTagAccess; + } + throw new \Exception(); + }); + + $this->systemTagManager->method('updateTag') + ->willReturnCallback(function ($tagId, $tagName, $userVisible, $userAssignable): void { + throw new TagAlreadyExistsException( + 'Tag ("' . $tagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists' + ); + }); + + $this->systemTagManager->expects($this->once()) + ->method('updateTag') + ->with( + $tagId, + $newTagName, + $newTagUserVisible, + $newTagUserAssignable, + '' + ); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + '<error>Tag ("' . $newTagName . '", ' . $newTagUserVisible . ', ' . $newTagUserAssignable . ') already exists</error>' + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } + + public function testNotFound(): void { + $tagId = '404'; + + $this->input->method('getArgument') + ->willReturnCallback(function ($arg) use ($tagId) { + if ($arg === 'id') { + return $tagId; + } + throw new \Exception(); + }); + + $this->systemTagManager->method('getTagsByIds') + ->with($tagId) + ->willReturn([]); + + $this->output->expects($this->once()) + ->method('writeln') + ->with( + '<error>Tag not found</error>' + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/SystemTag/ListCommandTest.php b/tests/Core/Command/SystemTag/ListCommandTest.php new file mode 100644 index 00000000000..e1ff8290633 --- /dev/null +++ b/tests/Core/Command/SystemTag/ListCommandTest.php @@ -0,0 +1,96 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\SystemTag; + +use OC\Core\Command\SystemTag\ListCommand; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ListCommandTest extends TestCase { + /** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */ + private $systemTagManager; + + /** @var ListCommand|\PHPUnit\Framework\MockObject\MockObject */ + private $command; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $input; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $output; + + protected function setUp(): void { + parent::setUp(); + + $this->systemTagManager = $this->createMock(ISystemTagManager::class); + $this->command = $this->getMockBuilder(ListCommand::class) + ->setConstructorArgs([$this->systemTagManager]) + ->onlyMethods(['writeArrayInOutputFormat']) + ->getMock(); + + $this->input = $this->createMock(InputInterface::class); + $this->output = $this->createMock(OutputInterface::class); + } + + public function testExecute(): void { + $tag1 = $this->createMock(ISystemTag::class); + $tag1->method('getId')->willReturn('1'); + $tag1->method('getName')->willReturn('public_tag'); + $tag1->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_PUBLIC); + $tag2 = $this->createMock(ISystemTag::class); + $tag2->method('getId')->willReturn('2'); + $tag2->method('getName')->willReturn('restricted_tag'); + $tag2->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_RESTRICTED); + $tag3 = $this->createMock(ISystemTag::class); + $tag3->method('getId')->willReturn('3'); + $tag3->method('getName')->willReturn('invisible_tag'); + $tag3->method('getAccessLevel')->willReturn(ISystemTag::ACCESS_LEVEL_INVISIBLE); + + $this->systemTagManager->method('getAllTags') + ->with( + null, + null + )->willReturn([$tag1, $tag2, $tag3]); + + $this->input->method('getOption') + ->willReturnCallback(function ($arg) { + if ($arg === 'visibilityFilter') { + return null; + } elseif ($arg === 'nameSearchPattern') { + return null; + } + throw new \Exception(); + }); + + $this->command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with( + $this->equalTo($this->input), + $this->equalTo($this->output), + [ + '1' => [ + 'name' => 'public_tag', + 'access' => 'public', + ], + '2' => [ + 'name' => 'restricted_tag', + 'access' => 'restricted', + ], + '3' => [ + 'name' => 'invisible_tag', + 'access' => 'invisible', + ] + ] + ); + + $this->invokePrivate($this->command, 'execute', [$this->input, $this->output]); + } +} diff --git a/tests/Core/Command/TwoFactorAuth/CleanupTest.php b/tests/Core/Command/TwoFactorAuth/CleanupTest.php new file mode 100644 index 00000000000..1d4731ff0c2 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/CleanupTest.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Core\Command\TwoFactorAuth; + +use OC\Core\Command\TwoFactorAuth\Cleanup; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class CleanupTest extends TestCase { + /** @var IRegistry|MockObject */ + private $registry; + + /** @var IUserManager|MockObject */ + private $userManager; + + /** @var CommandTester */ + private $cmd; + + protected function setUp(): void { + parent::setUp(); + + $this->registry = $this->createMock(IRegistry::class); + $this->userManager = $this->createMock(IUserManager::class); + + $cmd = new Cleanup($this->registry, $this->userManager); + $this->cmd = new CommandTester($cmd); + } + + public function testCleanup(): void { + $this->registry->expects($this->once()) + ->method('cleanUp') + ->with('u2f'); + + $rc = $this->cmd->execute([ + 'provider-id' => 'u2f', + ]); + + $this->assertEquals(0, $rc); + $output = $this->cmd->getDisplay(); + $this->assertStringContainsString('All user-provider associations for provider u2f have been removed', $output); + } +} diff --git a/tests/Core/Command/TwoFactorAuth/DisableTest.php b/tests/Core/Command/TwoFactorAuth/DisableTest.php new file mode 100644 index 00000000000..ab6b10f8964 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/DisableTest.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\ProviderManager; +use OC\Core\Command\TwoFactorAuth\Disable; +use OCP\IUser; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class DisableTest extends TestCase { + /** @var ProviderManager|MockObject */ + private $providerManager; + + /** @var IUserManager|MockObject */ + private $userManager; + + /** @var CommandTester */ + private $command; + + protected function setUp(): void { + parent::setUp(); + + $this->providerManager = $this->createMock(ProviderManager::class); + $this->userManager = $this->createMock(IUserManager::class); + + $cmd = new Disable($this->providerManager, $this->userManager); + $this->command = new CommandTester($cmd); + } + + public function testInvalidUID(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('nope') + ->willReturn(null); + + $rc = $this->command->execute([ + 'uid' => 'nope', + 'provider_id' => 'nope', + ]); + + $this->assertEquals(1, $rc); + $this->assertStringContainsString('Invalid UID', $this->command->getDisplay()); + } + + public function testEnableNotSupported(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('ricky') + ->willReturn($user); + $this->providerManager->expects($this->once()) + ->method('tryDisableProviderFor') + ->with('totp', $user) + ->willReturn(false); + + $rc = $this->command->execute([ + 'uid' => 'ricky', + 'provider_id' => 'totp', + ]); + + $this->assertEquals(2, $rc); + $this->assertStringContainsString('The provider does not support this operation', $this->command->getDisplay()); + } + + public function testEnabled(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('ricky') + ->willReturn($user); + $this->providerManager->expects($this->once()) + ->method('tryDisableProviderFor') + ->with('totp', $user) + ->willReturn(true); + + $rc = $this->command->execute([ + 'uid' => 'ricky', + 'provider_id' => 'totp', + ]); + + $this->assertEquals(0, $rc); + $this->assertStringContainsString('Two-factor provider totp disabled for user ricky', $this->command->getDisplay()); + } +} diff --git a/tests/Core/Command/TwoFactorAuth/EnableTest.php b/tests/Core/Command/TwoFactorAuth/EnableTest.php new file mode 100644 index 00000000000..7c34d6692c5 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/EnableTest.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Core\Command\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\ProviderManager; +use OC\Core\Command\TwoFactorAuth\Enable; +use OCP\IUser; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class EnableTest extends TestCase { + /** @var ProviderManager|MockObject */ + private $providerManager; + + /** @var IUserManager|MockObject */ + private $userManager; + + /** @var CommandTester */ + private $command; + + protected function setUp(): void { + parent::setUp(); + + $this->providerManager = $this->createMock(ProviderManager::class); + $this->userManager = $this->createMock(IUserManager::class); + + $cmd = new Enable($this->providerManager, $this->userManager); + $this->command = new CommandTester($cmd); + } + + public function testInvalidUID(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('nope') + ->willReturn(null); + + $rc = $this->command->execute([ + 'uid' => 'nope', + 'provider_id' => 'nope', + ]); + + $this->assertEquals(1, $rc); + $this->assertStringContainsString('Invalid UID', $this->command->getDisplay()); + } + + public function testEnableNotSupported(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('belle') + ->willReturn($user); + $this->providerManager->expects($this->once()) + ->method('tryEnableProviderFor') + ->with('totp', $user) + ->willReturn(false); + + $rc = $this->command->execute([ + 'uid' => 'belle', + 'provider_id' => 'totp', + ]); + + $this->assertEquals(2, $rc); + $this->assertStringContainsString('The provider does not support this operation', $this->command->getDisplay()); + } + + public function testEnabled(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('belle') + ->willReturn($user); + $this->providerManager->expects($this->once()) + ->method('tryEnableProviderFor') + ->with('totp', $user) + ->willReturn(true); + + $rc = $this->command->execute([ + 'uid' => 'belle', + 'provider_id' => 'totp', + ]); + + $this->assertEquals(0, $rc); + $this->assertStringContainsString('Two-factor provider totp enabled for user belle', $this->command->getDisplay()); + } +} diff --git a/tests/Core/Command/TwoFactorAuth/EnforceTest.php b/tests/Core/Command/TwoFactorAuth/EnforceTest.php new file mode 100644 index 00000000000..03118772377 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/EnforceTest.php @@ -0,0 +1,128 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\EnforcementState; +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OC\Core\Command\TwoFactorAuth\Enforce; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class EnforceTest extends TestCase { + /** @var MandatoryTwoFactor|MockObject */ + private $mandatoryTwoFactor; + + /** @var CommandTester */ + private $command; + + protected function setUp(): void { + parent::setUp(); + + $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); + $command = new Enforce($this->mandatoryTwoFactor); + + $this->command = new CommandTester($command); + } + + public function testEnforce(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setState') + ->with($this->equalTo(new EnforcementState(true))); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(true)); + + $rc = $this->command->execute([ + '--on' => true, + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is enforced for all users', $display); + } + + public function testEnforceForOneGroup(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setState') + ->with($this->equalTo(new EnforcementState(true, ['twofactorers']))); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(true, ['twofactorers'])); + + $rc = $this->command->execute([ + '--on' => true, + '--group' => ['twofactorers'], + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is enforced for members of the group(s) twofactorers', $display); + } + + public function testEnforceForAllExceptOneGroup(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setState') + ->with($this->equalTo(new EnforcementState(true, [], ['yoloers']))); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(true, [], ['yoloers'])); + + $rc = $this->command->execute([ + '--on' => true, + '--exclude' => ['yoloers'], + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is enforced for all users, except members of yoloers', $display); + } + + public function testDisableEnforced(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setState') + ->with(new EnforcementState(false)); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(false)); + + $rc = $this->command->execute([ + '--off' => true, + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is not enforced', $display); + } + + public function testCurrentStateEnabled(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(true)); + + $rc = $this->command->execute([]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is enforced for all users', $display); + } + + public function testCurrentStateDisabled(): void { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('getState') + ->willReturn(new EnforcementState(false)); + + $rc = $this->command->execute([]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is not enforced', $display); + } +} diff --git a/tests/Core/Command/TwoFactorAuth/StateTest.php b/tests/Core/Command/TwoFactorAuth/StateTest.php new file mode 100644 index 00000000000..f4ca3c4e031 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/StateTest.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Core\Command\TwoFactorAuth; + +use OC\Core\Command\TwoFactorAuth\State; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\IUser; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class StateTest extends TestCase { + /** @var IRegistry|MockObject */ + private $registry; + + /** @var IUserManager|MockObject */ + private $userManager; + + /** @var CommandTester|MockObject */ + private $cmd; + + protected function setUp(): void { + parent::setUp(); + + $this->registry = $this->createMock(IRegistry::class); + $this->userManager = $this->createMock(IUserManager::class); + + $cmd = new State($this->registry, $this->userManager); + $this->cmd = new CommandTester($cmd); + } + + public function testWrongUID(): void { + $this->cmd->execute([ + 'uid' => 'nope', + ]); + + $output = $this->cmd->getDisplay(); + $this->assertStringContainsString('Invalid UID', $output); + } + + public function testStateNoProvidersActive(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('eldora') + ->willReturn($user); + $states = [ + 'u2f' => false, + 'totp' => false, + ]; + $this->registry->expects($this->once()) + ->method('getProviderStates') + ->with($user) + ->willReturn($states); + + $this->cmd->execute([ + 'uid' => 'eldora', + ]); + + $output = $this->cmd->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is not enabled for user eldora', $output); + } + + public function testStateOneProviderActive(): void { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('mohamed') + ->willReturn($user); + $states = [ + 'u2f' => true, + 'totp' => false, + ]; + $this->registry->expects($this->once()) + ->method('getProviderStates') + ->with($user) + ->willReturn($states); + + $this->cmd->execute([ + 'uid' => 'mohamed', + ]); + + $output = $this->cmd->getDisplay(); + $this->assertStringContainsString('Two-factor authentication is enabled for user mohamed', $output); + } +} diff --git a/tests/Core/Command/User/AddTest.php b/tests/Core/Command/User/AddTest.php new file mode 100644 index 00000000000..5a8bc3abea1 --- /dev/null +++ b/tests/Core/Command/User/AddTest.php @@ -0,0 +1,152 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +declare(strict_types=1); + +namespace Core\Command\User; + +use OC\Core\Command\User\Add; +use OCA\Settings\Mailer\NewUserMailHelper; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAppConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Mail\IEMailTemplate; +use OCP\mail\IMailer; +use OCP\Security\ISecureRandom; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class AddTest extends TestCase { + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; + + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ + private $mailer; + + /** @var IAppConfig|\PHPUnit\Framework\MockObject\MockObject */ + private $appConfig; + + /** @var NewUserMailHelper|\PHPUnit\Framework\MockObject\MockObject */ + private $mailHelper; + + /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */ + private $eventDispatcher; + + /** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */ + private $secureRandom; + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ + private $user; + + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $consoleInput; + + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $consoleOutput; + + /** @var Add */ + private $addCommand; + + public function setUp(): void { + parent::setUp(); + + $this->userManager = static::createMock(IUserManager::class); + $this->groupManager = static::createStub(IGroupManager::class); + $this->mailer = static::createMock(IMailer::class); + $this->appConfig = static::createMock(IAppConfig::class); + $this->mailHelper = static::createMock(NewUserMailHelper::class); + $this->eventDispatcher = static::createStub(IEventDispatcher::class); + $this->secureRandom = static::createStub(ISecureRandom::class); + + $this->user = static::createMock(IUser::class); + + $this->consoleInput = static::createMock(InputInterface::class); + $this->consoleOutput = static::createMock(OutputInterface::class); + + $this->addCommand = new Add( + $this->userManager, + $this->groupManager, + $this->mailer, + $this->appConfig, + $this->mailHelper, + $this->eventDispatcher, + $this->secureRandom + ); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('addEmailDataProvider')] + public function testAddEmail( + ?string $email, + bool $isEmailValid, + bool $shouldSendEmail, + ): void { + $this->user->expects($isEmailValid ? static::once() : static::never()) + ->method('setSystemEMailAddress') + ->with(static::equalTo($email)); + + $this->userManager->method('createUser') + ->willReturn($this->user); + + $this->appConfig->method('getValueString') + ->willReturn($shouldSendEmail ? 'yes' : 'no'); + + $this->mailer->method('validateMailAddress') + ->willReturn($isEmailValid); + + $this->mailHelper->method('generateTemplate') + ->willReturn(static::createMock(IEMailTemplate::class)); + + $this->mailHelper->expects($isEmailValid && $shouldSendEmail ? static::once() : static::never()) + ->method('sendMail'); + + $this->consoleInput->method('getOption') + ->willReturnMap([ + ['generate-password', 'true'], + ['email', $email], + ['group', []], + ]); + + $this->invokePrivate($this->addCommand, 'execute', [ + $this->consoleInput, + $this->consoleOutput + ]); + } + + /** + * @return array + */ + public static function addEmailDataProvider(): array { + return [ + 'Valid E-Mail' => [ + 'info@example.com', + true, + true, + ], + 'Invalid E-Mail' => [ + 'info@@example.com', + false, + false, + ], + 'No E-Mail' => [ + '', + false, + false, + ], + 'Valid E-Mail, but no mail should be sent' => [ + 'info@example.com', + true, + false, + ], + ]; + } +} diff --git a/tests/Core/Command/User/AuthTokens/DeleteTest.php b/tests/Core/Command/User/AuthTokens/DeleteTest.php new file mode 100644 index 00000000000..6692473c240 --- /dev/null +++ b/tests/Core/Command/User/AuthTokens/DeleteTest.php @@ -0,0 +1,166 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Tests\Core\Command\User\AuthTokens; + +use OC\Authentication\Token\IProvider; +use OC\Core\Command\User\AuthTokens\Delete; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $tokenProvider; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $tokenProvider = $this->tokenProvider = $this->getMockBuilder(IProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var \OC\Authentication\Token\IProvider $tokenProvider */ + $this->command = new Delete($tokenProvider); + } + + public function testDeleteTokenById(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', '42'] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn(null); + + $this->tokenProvider->expects($this->once()) + ->method('invalidateTokenById') + ->with('user', 42); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::SUCCESS, $result); + } + + public function testDeleteTokenByIdRequiresTokenId(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn(null); + + $this->expectException(RuntimeException::class); + + $this->tokenProvider->expects($this->never())->method('invalidateTokenById'); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::FAILURE, $result); + } + + public function testDeleteTokensLastUsedBefore(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn('946684800'); + + $this->tokenProvider->expects($this->once()) + ->method('invalidateLastUsedBefore') + ->with('user', 946684800); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::SUCCESS, $result); + } + + public function testLastUsedBeforeAcceptsIso8601Expanded(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn('2000-01-01T00:00:00Z'); + + $this->tokenProvider->expects($this->once()) + ->method('invalidateLastUsedBefore') + ->with('user', 946684800); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::SUCCESS, $result); + } + + public function testLastUsedBeforeAcceptsYmd(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn('2000-01-01'); + + $this->tokenProvider->expects($this->once()) + ->method('invalidateLastUsedBefore') + ->with('user', 946684800); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::SUCCESS, $result); + } + + public function testIdAndLastUsedBeforeAreMutuallyExclusive(): void { + $this->consoleInput->expects($this->exactly(2)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'user'], + ['id', '42'] + ]); + + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('last-used-before') + ->willReturn('946684800'); + + $this->expectException(RuntimeException::class); + + $this->tokenProvider->expects($this->never())->method('invalidateLastUsedBefore'); + + $result = self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + $this->assertSame(Command::SUCCESS, $result); + } +} diff --git a/tests/Core/Command/User/DeleteTest.php b/tests/Core/Command/User/DeleteTest.php new file mode 100644 index 00000000000..4e06b0f91fc --- /dev/null +++ b/tests/Core/Command/User/DeleteTest.php @@ -0,0 +1,96 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\User; + +use OC\Core\Command\User\Delete; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DeleteTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $userManager = $this->userManager = $this->getMockBuilder(IUserManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var IUserManager $userManager */ + $this->command = new Delete($userManager); + } + + + public static function validUserLastSeen(): array { + return [ + [true, 'The specified user was deleted'], + [false, 'The specified user could not be deleted'], + ]; + } + + /** + * + * @param bool $deleteSuccess + * @param string $expectedString + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validUserLastSeen')] + public function testValidUser($deleteSuccess, $expectedString): void { + $user = $this->getMockBuilder(IUser::class)->getMock(); + $user->expects($this->once()) + ->method('delete') + ->willReturn($deleteSuccess); + + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn($user); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains($expectedString)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testInvalidUser(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn(null); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('User does not exist')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/User/DisableTest.php b/tests/Core/Command/User/DisableTest.php new file mode 100644 index 00000000000..c1bc10dc6bf --- /dev/null +++ b/tests/Core/Command/User/DisableTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\User; + +use OC\Core\Command\User\Disable; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class DisableTest extends TestCase { + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var Disable */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new Disable($this->userManager); + } + + public function testValidUser(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once()) + ->method('setEnabled') + ->with(false); + + $this->userManager + ->method('get') + ->with('user') + ->willReturn($user); + + $this->consoleInput + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('The specified user is disabled')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testInvalidUser(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn(null); + + $this->consoleInput + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('User does not exist')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/User/EnableTest.php b/tests/Core/Command/User/EnableTest.php new file mode 100644 index 00000000000..b2820de14ef --- /dev/null +++ b/tests/Core/Command/User/EnableTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Tests\Core\Command\User; + +use OC\Core\Command\User\Enable; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class EnableTest extends TestCase { + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + /** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var Disable */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + + $this->command = new Enable($this->userManager); + } + + public function testValidUser(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once()) + ->method('setEnabled') + ->with(true); + + $this->userManager + ->method('get') + ->with('user') + ->willReturn($user); + + $this->consoleInput + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('The specified user is enabled')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testInvalidUser(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn(null); + + $this->consoleInput + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('User does not exist')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/User/LastSeenTest.php b/tests/Core/Command/User/LastSeenTest.php new file mode 100644 index 00000000000..64c710eacc5 --- /dev/null +++ b/tests/Core/Command/User/LastSeenTest.php @@ -0,0 +1,95 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\User; + +use OC\Core\Command\User\LastSeen; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class LastSeenTest extends TestCase { + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleInput; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp(): void { + parent::setUp(); + + $userManager = $this->userManager = $this->getMockBuilder(IUserManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); + $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); + + /** @var IUserManager $userManager */ + $this->command = new LastSeen($userManager); + } + + public static function validUserLastSeen(): array { + return [ + [0, 'never logged in'], + [time(), 'last login'], + ]; + } + + /** + * + * @param int $lastSeen + * @param string $expectedString + */ + #[\PHPUnit\Framework\Attributes\DataProvider('validUserLastSeen')] + public function testValidUser($lastSeen, $expectedString): void { + $user = $this->getMockBuilder(IUser::class)->getMock(); + $user->expects($this->once()) + ->method('getLastLogin') + ->willReturn($lastSeen); + + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn($user); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains($expectedString)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testInvalidUser(): void { + $this->userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn(null); + + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('uid') + ->willReturn('user'); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($this->stringContains('User does not exist')); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +} diff --git a/tests/Core/Command/User/ProfileTest.php b/tests/Core/Command/User/ProfileTest.php new file mode 100644 index 00000000000..ff5568bacfc --- /dev/null +++ b/tests/Core/Command/User/ProfileTest.php @@ -0,0 +1,465 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Core\Command\User; + +use OC\Core\Command\User\Profile; +use OCP\Accounts\IAccount; +use OCP\Accounts\IAccountManager; +use OCP\Accounts\IAccountProperty; +use OCP\IDBConnection; +use OCP\IUser; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class ProfileTest extends TestCase { + + protected IAccountManager&MockObject $accountManager; + protected IUserManager&MockObject $userManager; + protected IDBConnection&MockObject $connection; + protected InputInterface&MockObject $consoleInput; + protected OutputInterface&MockObject $consoleOutput; + + protected function setUp(): void { + parent::setUp(); + + $this->accountManager = $this->createMock(IAccountManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->connection = $this->createMock(IDBConnection::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + } + + public function getCommand(array $methods = []): Profile|MockObject { + if (empty($methods)) { + return new Profile($this->userManager, $this->accountManager); + } else { + return $this->getMockBuilder(Profile::class) + ->setConstructorArgs([ + $this->userManager, + $this->accountManager, + ]) + ->onlyMethods($methods) + ->getMock(); + } + } + + public static function dataCheckInput(): array { + return [ + 'Call with existing user should pass check' => [ + [['uid', 'username']], + [], + [], + true, + null, + ], + 'Call with non-existing user should fail check' => [ + [['uid', 'username']], + [], + [], + false, + 'The user "username" does not exist.', + ], + + 'Call with uid, key and --default value should pass check' => [ + [['uid', 'username'], ['key', 'configkey']], + [], + [['--default-value', false, true]], + true, + null, + ], + 'Call with uid and empty key with default-value option should fail check' => [ + [['uid', 'username'], ['key', '']], + [], + [['--default-value', false, true]], + true, + 'The "default-value" option can only be used when specifying a key.', + ], + + 'Call with uid, key, value should pass check' => [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [], + [], + true, + null, + ], + 'Call with uid, empty key and empty value should fail check' => [ + [['uid', 'username'], ['key', ''], ['value', '']], + [], + [], + true, + 'The value argument can only be used when specifying a key.', + ], + 'Call with uid, key, empty value and default-value option should fail check' => [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [], + [['--default-value', false, true]], + true, + 'The value argument can not be used together with "default-value".', + ], + 'Call with uid, key, empty value and update-only option should pass check' => [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['update-only', true]], + [], + true, + null, + ], + 'Call with uid, key, null value and update-only option should fail check' => [ + [['uid', 'username'], ['key', 'configkey'], ['value', null]], + [['update-only', true]], + [], + true, + 'The "update-only" option can only be used together with "value".', + ], + + 'Call with uid, key and delete option should pass check' => [ + [['uid', 'username'], ['key', 'configkey']], + [['delete', true]], + [], + true, + null, + ], + 'Call with uid, empty key and delete option should fail check' => [ + [['uid', 'username'], ['key', '']], + [['delete', true]], + [], + true, + 'The "delete" option can only be used when specifying a key.', + ], + 'Call with uid, key, delete option and default-value should fail check' => [ + [['uid', 'username'], ['key', 'configkey']], + [['delete', true]], + [['--default-value', false, true]], + true, + 'The "delete" option can not be used together with "default-value".', + ], + 'Call with uid, key, empty value and delete option should fail check' => [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['delete', true]], + [], + true, + 'The "delete" option can not be used together with "value".', + ], + 'Call with uid, key, delete and error-if-not-exists should pass check' => [ + [['uid', 'username'], ['key', 'configkey']], + [['delete', true], ['error-if-not-exists', true]], + [], + true, + null, + ], + 'Call with uid, key and error-if-not-exists should fail check' => [ + [['uid', 'username'], ['key', 'configkey']], + [['delete', false], ['error-if-not-exists', true]], + [], + true, + 'The "error-if-not-exists" option can only be used together with "delete".', + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCheckInput')] + public function testCheckInput(array $arguments, array $options, array $parameterOptions, bool $existingUser, ?string $expectedException): void { + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap($arguments); + $this->consoleInput->expects($this->any()) + ->method('getOption') + ->willReturnMap($options); + $this->consoleInput->expects($this->any()) + ->method('hasParameterOption') + ->willReturnCallback(function (string|array $values, bool $onlyParams = false) use ($parameterOptions): bool { + $arguments = func_get_args(); + foreach ($parameterOptions as $parameterOption) { + // check the arguments of the function, if they are the same, return the mocked value + if (array_diff($arguments, $parameterOption) === []) { + return end($parameterOption); + } + } + + return false; + }); + + $returnedUser = null; + if ($existingUser) { + $mockUser = $this->createMock(IUser::class); + $mockUser->expects($this->once())->method('getUID')->willReturn('user'); + $returnedUser = $mockUser; + } + $this->userManager->expects($this->once()) + ->method('get') + ->willReturn($returnedUser); + + $command = $this->getCommand(); + try { + $this->invokePrivate($command, 'checkInput', [$this->consoleInput]); + $this->assertNull($expectedException); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($expectedException, $e->getMessage()); + } + } + + public function testCheckInputExceptionCatch(): void { + $command = $this->getCommand(['checkInput']); + $command->expects($this->once()) + ->method('checkInput') + ->willThrowException(new \InvalidArgumentException('test')); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with('<error>test</error>'); + + $this->assertEquals(1, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function dataExecuteDeleteProfileProperty(): array { + return [ + 'Deleting existing property should succeed' => ['address', 'Berlin', false, null, Command::SUCCESS], + 'Deleting existing property with error-if-not-exists should succeed' => ['address', 'Berlin', true, null, Command::SUCCESS], + 'Deleting non-existing property should succeed' => ['address', '', false, null, Command::SUCCESS], + 'Deleting non-existing property with error-if-not-exists should fail' => ['address', '', true, '<error>The property does not exist for user "username".</error>', Command::FAILURE], + ]; + } + + /** + * Tests the deletion mechanism on profile settings. + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecuteDeleteProfileProperty')] + public function testExecuteDeleteProfileProperty(string $configKey, string $value, bool $errorIfNotExists, ?string $expectedLine, int $expectedReturn): void { + $uid = 'username'; + $appName = 'profile'; + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + ]); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', $uid], + ['app', $appName], + ['key', $configKey], + ]); + + $mocks = $this->setupProfilePropertiesMock([$configKey => $value]); + + $command->expects($this->once()) + ->method('checkInput') + ->willReturn($mocks['userMock']); + + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturnMap([ + ['--delete', false, true], + ['--error-if-not-exists', false, $errorIfNotExists], + ]); + + if ($expectedLine === null) { + $this->consoleOutput->expects($this->never()) + ->method('writeln'); + $mocks['profilePropertiesMocks'][0]->expects($this->once()) + ->method('setValue') + ->with(''); + $this->accountManager->expects($this->once()) + ->method('updateAccount') + ->with($mocks['accountMock']); + } else { + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($expectedLine); + $this->accountManager->expects($this->never()) + ->method('updateAccount'); + } + + $this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public function testExecuteSetProfileProperty(): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + ]); + + $uid = 'username'; + $propertyKey = 'address'; + $propertyValue = 'Barcelona'; + + $this->consoleInput->expects($this->atLeast(3)) + ->method('getArgument') + ->willReturnMap([ + ['uid', $uid], + ['key', $propertyKey], + ['value', $propertyValue], + ]); + + $mocks = $this->setupProfilePropertiesMock([$propertyKey => $propertyValue]); + + $command->expects($this->once()) + ->method('checkInput') + ->willReturn($mocks['userMock']); + + $mocks['profilePropertiesMocks'][0]->expects($this->once()) + ->method('setValue') + ->with($propertyValue); + $this->accountManager->expects($this->once()) + ->method('updateAccount') + ->with($mocks['accountMock']); + + $this->assertEquals(0, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function dataExecuteGet(): array { + return [ + 'Get property with set value should pass' => ['configkey', 'value', null, 'value', Command::SUCCESS], + 'Get property with empty value and default-value option should pass' => ['configkey', '', 'default-value', 'default-value', Command::SUCCESS], + 'Get property with empty value should fail' => ['configkey', '', null, '<error>The property does not exist for user "username".</error>', Command::FAILURE], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecuteGet')] + public function testExecuteGet(string $key, string $value, ?string $defaultValue, string $expectedLine, int $expectedReturn): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + ]); + + $uid = 'username'; + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', $uid], + ['key', $key], + ]); + + $mocks = $this->setupProfilePropertiesMock([$key => $value]); + + $command->expects($this->once()) + ->method('checkInput') + ->willReturn($mocks['userMock']); + + if ($value === '') { + if ($defaultValue === null) { + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturn(false); + } else { + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturnCallback(fn (string|array $values): bool => $values === '--default-value'); + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('default-value') + ->willReturn($defaultValue); + } + } + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($expectedLine); + + $this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public function testExecuteList(): void { + $uid = 'username'; + $profileData = [ + 'pronouns' => 'they/them', + 'address' => 'Berlin', + ]; + + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + ]); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', $uid], + ['key', ''], + ]); + + $mocks = $this->setupProfilePropertiesMock(['address' => $profileData['address'], 'pronouns' => $profileData['pronouns']]); + + $command->expects($this->once()) + ->method('checkInput') + ->willReturn($mocks['userMock']); + + $command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with($this->consoleInput, $this->consoleOutput, $profileData); + + + $this->assertEquals(0, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + /** + * Helper to avoid boilerplate in tests in this file when mocking objects + * of IAccountProperty type. + * + * @param array<string, string> $properties the properties to be set up as key => value + * @return array{ + * userMock: IUser&MockObject, + * accountMock: IAccount&MockObject, + * profilePropertiesMocks: IAccountProperty&MockObject[] + * } + */ + private function setupProfilePropertiesMock(array $properties): array { + $userMock = $this->createMock(IUser::class); + $accountMock = $this->createMock(IAccount::class); + $this->accountManager->expects($this->atLeastOnce()) + ->method('getAccount') + ->with($userMock) + ->willReturn($accountMock); + + /** @var IAccountProperty&MockObject[] $propertiesMocks */ + $propertiesMocks = []; + foreach ($properties as $key => $value) { + $propertiesMocks[] = $this->getAccountPropertyMock($key, $value); + } + + if (count($properties) === 1) { + $accountMock->expects($this->atLeastOnce()) + ->method('getProperty') + ->with(array_keys($properties)[0]) + ->willReturn($propertiesMocks[array_key_first($propertiesMocks)]); + } else { + $accountMock->expects($this->atLeastOnce()) + ->method('getAllProperties') + ->willReturnCallback(function () use ($propertiesMocks) { + foreach ($propertiesMocks as $property) { + yield $property; + } + }); + } + + return [ + 'userMock' => $userMock, + 'accountMock' => $accountMock, + 'profilePropertiesMocks' => $propertiesMocks, + ]; + } + + private function getAccountPropertyMock(string $name, string $value): IAccountProperty&MockObject { + $propertyMock = $this->getMockBuilder(IAccountProperty::class) + ->disableOriginalConstructor() + ->getMock(); + $propertyMock->expects($this->any()) + ->method('getName') + ->willReturn($name); + $propertyMock->expects($this->any()) + ->method('getValue') + ->willReturn($value); + + return $propertyMock; + } +} diff --git a/tests/Core/Command/User/SettingTest.php b/tests/Core/Command/User/SettingTest.php new file mode 100644 index 00000000000..706e5b24742 --- /dev/null +++ b/tests/Core/Command/User/SettingTest.php @@ -0,0 +1,452 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Tests\Core\Command\User; + +use InvalidArgumentException; +use OC\Core\Command\User\Setting; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +class SettingTest extends TestCase { + protected IUserManager&MockObject $userManager; + protected IConfig&MockObject $config; + protected IDBConnection&MockObject $connection; + protected InputInterface&MockObject $consoleInput; + protected MockObject&OutputInterface $consoleOutput; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->config = $this->createMock(IConfig::class); + $this->connection = $this->createMock(IDBConnection::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + } + + public function getCommand(array $methods = []) { + if (empty($methods)) { + return new Setting($this->userManager, $this->config); + } else { + $mock = $this->getMockBuilder(Setting::class) + ->setConstructorArgs([ + $this->userManager, + $this->config, + ]) + ->onlyMethods($methods) + ->getMock(); + return $mock; + } + } + + public static function dataCheckInput(): array { + return [ + [ + [['uid', 'username']], + [['ignore-missing-user', true]], + [], + false, + false, + ], + [ + [['uid', 'username']], + [['ignore-missing-user', false]], + [], + null, + 'The user "username" does not exist.', + ], + + [ + [['uid', 'username'], ['key', 'configkey']], + [['ignore-missing-user', true]], + [['--default-value', false, true]], + false, + false, + ], + [ + [['uid', 'username'], ['key', '']], + [['ignore-missing-user', true]], + [['--default-value', false, true]], + false, + 'The "default-value" option can only be used when specifying a key.', + ], + + [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['ignore-missing-user', true]], + [], + false, + false, + ], + [ + [['uid', 'username'], ['key', ''], ['value', '']], + [['ignore-missing-user', true]], + [], + false, + 'The value argument can only be used when specifying a key.', + ], + [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['ignore-missing-user', true]], + [['--default-value', false, true]], + false, + 'The value argument can not be used together with "default-value".', + ], + [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['ignore-missing-user', true], ['update-only', true]], + [], + false, + false, + ], + [ + [['uid', 'username'], ['key', 'configkey'], ['value', null]], + [['ignore-missing-user', true], ['update-only', true]], + [], + false, + 'The "update-only" option can only be used together with "value".', + ], + + [ + [['uid', 'username'], ['key', 'configkey']], + [['ignore-missing-user', true], ['delete', true]], + [], + false, + false, + ], + [ + [['uid', 'username'], ['key', '']], + [['ignore-missing-user', true], ['delete', true]], + [], + false, + 'The "delete" option can only be used when specifying a key.', + ], + [ + [['uid', 'username'], ['key', 'configkey']], + [['ignore-missing-user', true], ['delete', true]], + [['--default-value', false, true]], + false, + 'The "delete" option can not be used together with "default-value".', + ], + [ + [['uid', 'username'], ['key', 'configkey'], ['value', '']], + [['ignore-missing-user', true], ['delete', true]], + [], + false, + 'The "delete" option can not be used together with "value".', + ], + [ + [['uid', 'username'], ['key', 'configkey']], + [['ignore-missing-user', true], ['delete', true], ['error-if-not-exists', true]], + [], + false, + false, + ], + [ + [['uid', 'username'], ['key', 'configkey']], + [['ignore-missing-user', true], ['delete', false], ['error-if-not-exists', true]], + [], + false, + 'The "error-if-not-exists" option can only be used together with "delete".', + ], + ]; + } + + /** + * + * @param array $arguments + * @param array $options + * @param array $parameterOptions + * @param mixed $user + * @param string $expectedException + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataCheckInput')] + public function testCheckInput($arguments, $options, $parameterOptions, $user, $expectedException): void { + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap($arguments); + $this->consoleInput->expects($this->any()) + ->method('getOption') + ->willReturnMap($options); + $this->consoleInput->expects($this->any()) + ->method('hasParameterOption') + ->willReturnCallback(function (string|array $config, bool $default = false) use ($parameterOptions): bool { + foreach ($parameterOptions as $parameterOption) { + if ($config === $parameterOption[0] + // Check the default value if the maps has 3 entries + && (!isset($parameterOption[2]) || $default === $parameterOption[1])) { + return end($parameterOption); + } + } + return false; + }); + + if ($user !== false) { + $this->userManager->expects($this->once()) + ->method('get') + ->willReturn($user); + } else { + $this->userManager->expects($this->never()) + ->method('get'); + } + + $command = $this->getCommand(); + try { + $this->invokePrivate($command, 'checkInput', [$this->consoleInput]); + $this->assertFalse($expectedException); + } catch (InvalidArgumentException $e) { + $this->assertEquals($expectedException, $e->getMessage()); + } + } + + public function testCheckInputExceptionCatch(): void { + $command = $this->getCommand(['checkInput']); + $command->expects($this->once()) + ->method('checkInput') + ->willThrowException(new InvalidArgumentException('test')); + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with('<error>test</error>'); + + $this->assertEquals(1, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function dataExecuteDelete(): array { + return [ + ['config', false, null, 0], + ['config', true, null, 0], + [null, false, null, 0], + [null, true, '<error>The setting does not exist for user "username".</error>', 1], + ]; + } + + /** + * + * @param string|null $value + * @param bool $errorIfNotExists + * @param string $expectedLine + * @param int $expectedReturn + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecuteDelete')] + public function testExecuteDelete($value, $errorIfNotExists, $expectedLine, $expectedReturn): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + 'getUserSettings', + ]); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'username'], + ['app', 'appname'], + ['key', 'configkey'], + ]); + + $command->expects($this->once()) + ->method('checkInput'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('username', 'appname', 'configkey', null) + ->willReturn($value); + + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturnMap([ + ['--delete', false, true], + ['--error-if-not-exists', false, $errorIfNotExists], + ]); + + if ($expectedLine === null) { + $this->consoleOutput->expects($this->never()) + ->method('writeln'); + $this->config->expects($this->once()) + ->method('deleteUserValue') + ->with('username', 'appname', 'configkey'); + } else { + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($expectedLine); + $this->config->expects($this->never()) + ->method('deleteUserValue'); + } + + $this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function dataExecuteSet(): array { + return [ + ['config', false, null, 0], + ['config', true, null, 0], + [null, false, null, 0], + [null, true, '<error>The setting does not exist for user "username".</error>', 1], + ]; + } + + /** + * + * @param string|null $value + * @param bool $updateOnly + * @param string $expectedLine + * @param int $expectedReturn + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecuteSet')] + public function testExecuteSet($value, $updateOnly, $expectedLine, $expectedReturn): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + 'getUserSettings', + ]); + + $this->consoleInput->expects($this->atLeast(4)) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'username'], + ['app', 'appname'], + ['key', 'configkey'], + ['value', 'setValue'], + ]); + + $command->expects($this->once()) + ->method('checkInput'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('username', 'appname', 'configkey', null) + ->willReturn($value); + + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturnMap([ + ['--update-only', false, $updateOnly], + ]); + + if ($expectedLine === null) { + $this->consoleOutput->expects($this->never()) + ->method('writeln'); + + $this->consoleInput->expects($this->never()) + ->method('getOption'); + + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('username', 'appname', 'configkey', 'setValue'); + } else { + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($expectedLine); + $this->config->expects($this->never()) + ->method('setUserValue'); + } + + $this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public static function dataExecuteGet(): array { + return [ + ['config', null, 'config', 0], + [null, 'config', 'config', 0], + [null, null, '<error>The setting does not exist for user "username".</error>', 1], + ]; + } + + /** + * + * @param string|null $value + * @param string|null $defaultValue + * @param string $expectedLine + * @param int $expectedReturn + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecuteGet')] + public function testExecuteGet($value, $defaultValue, $expectedLine, $expectedReturn): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + 'getUserSettings', + ]); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'username'], + ['app', 'appname'], + ['key', 'configkey'], + ]); + + $command->expects($this->once()) + ->method('checkInput'); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('username', 'appname', 'configkey', null) + ->willReturn($value); + + if ($value === null) { + if ($defaultValue === null) { + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturn(false); + } else { + $this->consoleInput->expects($this->atLeastOnce()) + ->method('hasParameterOption') + ->willReturnCallback(function (string|array $config, bool $default = false): bool { + if ($config === '--default-value' && $default === false) { + return true; + } + return false; + }); + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('default-value') + ->willReturn($defaultValue); + } + } + + $this->consoleOutput->expects($this->once()) + ->method('writeln') + ->with($expectedLine); + + $this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } + + public function testExecuteList(): void { + $command = $this->getCommand([ + 'writeArrayInOutputFormat', + 'checkInput', + 'getUserSettings', + ]); + + $this->consoleInput->expects($this->any()) + ->method('getArgument') + ->willReturnMap([ + ['uid', 'username'], + ['app', 'appname'], + ['key', ''], + ]); + + $command->expects($this->once()) + ->method('checkInput'); + $command->expects($this->once()) + ->method('getUserSettings') + ->willReturn(['settings']); + $command->expects($this->once()) + ->method('writeArrayInOutputFormat') + ->with($this->consoleInput, $this->consoleOutput, ['settings']); + + + $this->assertEquals(0, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); + } +} |