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".', ], ]; } /** * @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('test'); $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, 'The property does not exist for user "username".', Command::FAILURE], ]; } /** * Tests the deletion mechanism on profile settings. * * @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, 'The property does not exist for user "username".', Command::FAILURE], ]; } /** * @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 $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; } }