Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions lib/Service/OpenProjectAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1099,10 +1099,31 @@ public function getGroupFolderManager(): FolderManager {
return $groupfoldersFolderManager;
}

/**
* @param mixed $folder
* @return array<mixed>
*/
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;
}
Expand Down Expand Up @@ -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 &&
Expand Down
14 changes: 8 additions & 6 deletions tests/lib/Controller/OpenProjectControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
64 changes: 59 additions & 5 deletions tests/lib/Service/OpenProjectAPIServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class OpenProjectAPIServiceTest extends TestCase {
*/
private $service;

private MockObject $classExistsMock;

/**
* @var string
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -2553,7 +2561,7 @@ public function testIsSystemReadyForGroupFolderSetUpUserOrGroupExistsException(
->with('groupfolders', $userMock)
->willReturn($appEnabled);
$service = $this->getOpenProjectAPIServiceMock(
['getGroupFolderManager'],
['getGroupFolderManager', 'groupFolderToArray'],
[
'userManager' => $userManagerMock,
'groupManager' => $groupManagerMock,
Expand All @@ -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<mixed>
*/
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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down