diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a2709ed..2c040b423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Fixed + +- Consume groupfolder properties as an array [#851](https://github.com/nextcloud/integration_openproject/pull/851) + ## 2.9.1 - 2025-06-13 ### Fixed diff --git a/lib/Service/OpenProjectAPIService.php b/lib/Service/OpenProjectAPIService.php index 61d7c42a4..2739fdbf3 100644 --- a/lib/Service/OpenProjectAPIService.php +++ b/lib/Service/OpenProjectAPIService.php @@ -1099,10 +1099,31 @@ public function getGroupFolderManager(): FolderManager { return $groupfoldersFolderManager; } + /** + * @param mixed $folder + * @return array + */ + public function groupFolderToArray(mixed $folder): array { + // NOTE: groupfolders app has changed the return type in the recent versions + // for backwards compatibility we check if the new class exists + // and get the folder as an array + if (class_exists('\OCA\GroupFolders\Folder\FolderDefinition') && is_object($folder)) { + $folder = $folder->toArray(); + $folder['folder_id'] = $folder['id']; + } + if (!is_array($folder)) { + throw new InvalidArgumentException( + 'Invalid folder type. Expected array, got: ' . gettype($folder) + ); + } + return $folder; + } + public function isOpenProjectGroupfolderCreated(): bool { $groupfoldersFolderManager = $this->getGroupFolderManager(); $folders = $groupfoldersFolderManager->getAllFolders(); foreach ($folders as $folder) { + $folder = $this->groupFolderToArray($folder); if ($folder['mount_point'] === Application::OPEN_PROJECT_ENTITIES_NAME) { return true; } @@ -1261,6 +1282,7 @@ private function isGroupfolderAppCorrectlySetup():bool { $groupFolderManager = $this->getGroupFolderManager(); $folders = $groupFolderManager->getFoldersForGroup(Application::OPEN_PROJECT_ENTITIES_NAME); foreach ($folders as $folder) { + $folder = $this->groupFolderToArray($folder); if ( $folder['mount_point'] === Application::OPEN_PROJECT_ENTITIES_NAME && $folder['permissions'] === 31 && diff --git a/tests/lib/Controller/OpenProjectControllerTest.php b/tests/lib/Controller/OpenProjectControllerTest.php index 9a96badbd..c03a06359 100644 --- a/tests/lib/Controller/OpenProjectControllerTest.php +++ b/tests/lib/Controller/OpenProjectControllerTest.php @@ -13,6 +13,7 @@ use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\ServerException; +use GuzzleHttp\Psr7\Utils; use OCA\OpenProject\Service\OpenProjectAPIService; use OCP\Http\Client\IResponse; use OCP\Http\Client\LocalServerException; @@ -161,21 +162,22 @@ public function isValidOpenProjectInstanceExpectionDataProvider() { $requestMock = $this->getMockBuilder('\Psr\Http\Message\RequestInterface')->getMock(); $privateInstance = $this->getMockBuilder('\Psr\Http\Message\ResponseInterface')->getMock(); $privateInstance->method('getBody')->willReturn( - '{"_type":"Error","errorIdentifier":"urn:openproject-org:api:v3:errors:Unauthenticated"}' + Utils::streamFor('{"_type":"Error","errorIdentifier":"urn:openproject-org:api:v3:errors:Unauthenticated"}') ); $notOP = $this->getMockBuilder('\Psr\Http\Message\ResponseInterface')->getMock(); - $notOP->method('getBody')->willReturn('Unauthenticated'); + $notOPResponseBody = 'Unauthenticated'; + $notOP->method('getBody')->willReturn(Utils::streamFor($notOPResponseBody)); $notOP->method('getReasonPhrase')->willReturn('Unauthenticated'); - $notOP->method('getStatusCode')->willReturn('401'); + $notOP->method('getStatusCode')->willReturn(401); $notOPButJSON = $this->getMockBuilder('\Psr\Http\Message\ResponseInterface')->getMock(); $notOPButJSON->method('getBody')->willReturn( - '{"what":"Error","why":"Unauthenticated"}' + Utils::streamFor('{"what":"Error","why":"Unauthenticated"}') ); $notOPButJSON->method('getReasonPhrase')->willReturn('Unauthenticated'); - $notOPButJSON->method('getStatusCode')->willReturn('401'); + $notOPButJSON->method('getStatusCode')->willReturn(401); $otherResponseMock = $this->getMockBuilder('\Psr\Http\Message\ResponseInterface')->getMock(); $otherResponseMock->method('getReasonPhrase')->willReturn('Internal Server Error'); - $otherResponseMock->method('getStatusCode')->willReturn('500'); + $otherResponseMock->method('getStatusCode')->willReturn(500); return [ [ new ConnectException('a connection problem', $requestMock), diff --git a/tests/lib/Service/OpenProjectAPIServiceTest.php b/tests/lib/Service/OpenProjectAPIServiceTest.php index 9fcce1e04..7b51cfa9e 100644 --- a/tests/lib/Service/OpenProjectAPIServiceTest.php +++ b/tests/lib/Service/OpenProjectAPIServiceTest.php @@ -77,6 +77,8 @@ class OpenProjectAPIServiceTest extends TestCase { */ private $service; + private MockObject $classExistsMock; + /** * @var string */ @@ -603,6 +605,9 @@ public function getAppValues(array $withValues = []): array { * @before */ public function setupMockServer(): void { + // NOTE: mocking 'class_exists' must be done before anything else + $this->classExistsMock = $this->getFunctionMock(__NAMESPACE__, "class_exists"); + $this->pactMockServerConfig = new MockServerEnvConfig(); // find an unused port and use it for the mock server @@ -2237,6 +2242,7 @@ public function getFolderManagerMock( } public function testIsProjectFoldersSetupComplete(): void { + $this->classExistsMock->expects($this->any())->willReturn(true); $userMock = $this->createMock(IUser::class); $groupMock = $this->createMock(IGroup::class); $userManagerMock = $this->getMockBuilder(IUserManager::class) @@ -2456,6 +2462,7 @@ public function testGenerateAppPasswordTokenForUser(): void { } public function testIsSystemReadyForProjectFolderSetUp(): void { + $this->classExistsMock->expects($this->any())->willReturn(true); $userMock = $this->createMock(IUser::class); $userManagerMock = $this->getMockBuilder(IUserManager::class) ->getMock(); @@ -2529,6 +2536,7 @@ public function testIsSystemReadyForGroupFolderSetUpUserOrGroupExistsException( bool $groupFolderExists, string $exception ): void { + $this->classExistsMock->expects($this->any())->with('\OCA\GroupFolders\Folder\FolderManager')->willReturn($appEnabled); $userMock = $this->createMock(IUser::class); $userManagerMock = $this->getMockBuilder(IUserManager::class) ->getMock(); @@ -2553,7 +2561,7 @@ public function testIsSystemReadyForGroupFolderSetUpUserOrGroupExistsException( ->with('groupfolders', $userMock) ->willReturn($appEnabled); $service = $this->getOpenProjectAPIServiceMock( - ['getGroupFolderManager'], + ['getGroupFolderManager', 'groupFolderToArray'], [ 'userManager' => $userManagerMock, 'groupManager' => $groupManagerMock, @@ -2563,11 +2571,59 @@ public function testIsSystemReadyForGroupFolderSetUpUserOrGroupExistsException( $folderManagerMock = $this->getFolderManagerMock(); $service->method('getGroupFolderManager') ->willReturn($folderManagerMock); + $service->method('groupFolderToArray') + ->willReturn([ + 'id' => 123, + 'folder_id' => 123, + 'mount_point' => Application::OPEN_PROJECT_ENTITIES_NAME, + 'groups' => Application::OPEN_PROJECT_ENTITIES_NAME, + 'quota' => 1234, + 'size' => 0, + 'acl' => true, + 'permissions' => 31, + ]); $this->expectException(\Exception::class); $this->expectExceptionMessage($exception); $service->isSystemReadyForProjectFolderSetUp(); } + /** + * @return array + */ + public function groupFolderToArrayDataProvider(): array { + $folder = new class { + public function toArray(): array { + return ['id' => 123]; + } + }; + return [ + [true, $folder, ['id' => 123, 'folder_id' => 123]], + [false, ['id' => 123], ['id' => 123]], + [true, null, "Invalid folder type. Expected array, got: NULL"], + ]; + } + + /** + * @param bool $classExists + * @param mixed $folder + * @return void + * @dataProvider groupFolderToArrayDataProvider + */ + public function testGroupFolderToArray( + bool $classExists, + mixed $folder, + array|string $expectedResult, + ): void { + $this->classExistsMock->expects($this->any())->willReturn($classExists); + $service = $this->getOpenProjectAPIServiceMock(); + if ($folder === null) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($expectedResult); + } + $result = $service->groupFolderToArray($folder); + $this->assertSame($expectedResult, $result); + } + public function testProjectFolderHasAppPassword(): void { $tokenProviderMock = $this->getMockBuilder(IProvider::class)->disableOriginalConstructor() ->getMock(); @@ -4331,8 +4387,7 @@ public function testIsOIDCUser($hasOIDCBackend, $userBackend, $SSOProviderType, $configMock->method('getAppValue')->willReturn($SSOProviderType); if ($SSOProviderType !== OpenProjectAPIService::NEXTCLOUD_HUB_PROVIDER) { - $mock = $this->getFunctionMock(__NAMESPACE__, "class_exists"); - $mock->expects($this->once())->willReturn($hasOIDCBackend); + $this->classExistsMock->expects($this->once())->willReturn($hasOIDCBackend); } $userSessionMock = $this->createMock(IUserSession::class); @@ -4413,8 +4468,7 @@ public function dataProviderForIsUserOIDCAppSupported(): array { * @dataProvider dataProviderForIsUserOIDCAppSupported */ public function testIsUserOIDCAppSupported($appInstalledAndEnabled, $classesExist, $version, $expected): void { - $mock = $this->getFunctionMock(__NAMESPACE__, "class_exists"); - $mock->expects($this->any())->willReturn($classesExist); + $this->classExistsMock->expects($this->any())->willReturn($classesExist); $iAppManagerMock = $this->getMockBuilder(IAppManager::class)->getMock(); $iAppManagerMock->method('getAppVersion')->with('user_oidc')->willReturn($version);