aboutsummaryrefslogtreecommitdiffstats
path: root/tests/Core/Command
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Core/Command')
-rw-r--r--tests/Core/Command/Apps/AppsDisableTest.php71
-rw-r--r--tests/Core/Command/Apps/AppsEnableTest.php84
-rw-r--r--tests/Core/Command/Config/App/DeleteConfigTest.php99
-rw-r--r--tests/Core/Command/Config/App/GetConfigTest.php138
-rw-r--r--tests/Core/Command/Config/App/SetConfigTest.php107
-rw-r--r--tests/Core/Command/Config/ImportTest.php169
-rw-r--r--tests/Core/Command/Config/ListConfigsTest.php328
-rw-r--r--tests/Core/Command/Config/System/CastHelperTest.php66
-rw-r--r--tests/Core/Command/Config/System/DeleteConfigTest.php207
-rw-r--r--tests/Core/Command/Config/System/GetConfigTest.php161
-rw-r--r--tests/Core/Command/Config/System/SetConfigTest.php115
-rw-r--r--tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php360
-rw-r--r--tests/Core/Command/Encryption/DecryptAllTest.php216
-rw-r--r--tests/Core/Command/Encryption/DisableTest.php74
-rw-r--r--tests/Core/Command/Encryption/EnableTest.php101
-rw-r--r--tests/Core/Command/Encryption/EncryptAllTest.php94
-rw-r--r--tests/Core/Command/Encryption/SetDefaultModuleTest.php130
-rw-r--r--tests/Core/Command/Group/AddTest.php80
-rw-r--r--tests/Core/Command/Group/AddUserTest.php101
-rw-r--r--tests/Core/Command/Group/DeleteTest.php132
-rw-r--r--tests/Core/Command/Group/InfoTest.php98
-rw-r--r--tests/Core/Command/Group/ListCommandTest.php204
-rw-r--r--tests/Core/Command/Group/RemoveUserTest.php101
-rw-r--r--tests/Core/Command/Log/FileTest.php112
-rw-r--r--tests/Core/Command/Log/ManageTest.php164
-rw-r--r--tests/Core/Command/Maintenance/DataFingerprintTest.php53
-rw-r--r--tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php174
-rw-r--r--tests/Core/Command/Maintenance/ModeTest.php151
-rw-r--r--tests/Core/Command/Maintenance/UpdateTheme.php63
-rw-r--r--tests/Core/Command/Preview/CleanupTest.php175
-rw-r--r--tests/Core/Command/Preview/RepairTest.php153
-rw-r--r--tests/Core/Command/SystemTag/AddTest.php121
-rw-r--r--tests/Core/Command/SystemTag/DeleteTest.php83
-rw-r--r--tests/Core/Command/SystemTag/EditTest.php186
-rw-r--r--tests/Core/Command/SystemTag/ListCommandTest.php96
-rw-r--r--tests/Core/Command/TwoFactorAuth/CleanupTest.php52
-rw-r--r--tests/Core/Command/TwoFactorAuth/DisableTest.php94
-rw-r--r--tests/Core/Command/TwoFactorAuth/EnableTest.php94
-rw-r--r--tests/Core/Command/TwoFactorAuth/EnforceTest.php128
-rw-r--r--tests/Core/Command/TwoFactorAuth/StateTest.php94
-rw-r--r--tests/Core/Command/User/AddTest.php152
-rw-r--r--tests/Core/Command/User/AuthTokens/DeleteTest.php166
-rw-r--r--tests/Core/Command/User/DeleteTest.php96
-rw-r--r--tests/Core/Command/User/DisableTest.php78
-rw-r--r--tests/Core/Command/User/EnableTest.php78
-rw-r--r--tests/Core/Command/User/LastSeenTest.php95
-rw-r--r--tests/Core/Command/User/ProfileTest.php465
-rw-r--r--tests/Core/Command/User/SettingTest.php452
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]));
+ }
+}