From dfa0e3c6031295e9129db8e54a16146fff2bfbc5 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Fri, 28 Nov 2025 17:18:57 +0100 Subject: [PATCH 01/11] Added support for including system content type groups in ContentTypeController --- src/lib/Server/Controller/ContentType.php | 4 +- .../Controller/ContentTypeControllerTest.php | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/lib/Server/Controller/ContentTypeControllerTest.php diff --git a/src/lib/Server/Controller/ContentType.php b/src/lib/Server/Controller/ContentType.php index ae98ede9d..603c6a286 100644 --- a/src/lib/Server/Controller/ContentType.php +++ b/src/lib/Server/Controller/ContentType.php @@ -168,8 +168,10 @@ public function loadContentTypeGroupList(Request $request) ); } + $includeSystem = $request->query->getBoolean('includeSystem', false); + return new Values\ContentTypeGroupList( - $this->contentTypeService->loadContentTypeGroups(Language::ALL) + $this->contentTypeService->loadContentTypeGroups(Language::ALL, $includeSystem) ); } diff --git a/tests/lib/Server/Controller/ContentTypeControllerTest.php b/tests/lib/Server/Controller/ContentTypeControllerTest.php new file mode 100644 index 000000000..bb59fcd43 --- /dev/null +++ b/tests/lib/Server/Controller/ContentTypeControllerTest.php @@ -0,0 +1,56 @@ +createMock(ContentTypeService::class); + $contentTypeGroups = [$this->createMock(ContentTypeGroup::class)]; + + $contentTypeService + ->expects(self::once()) + ->method('loadContentTypeGroups') + ->with(Language::ALL, false) + ->willReturn($contentTypeGroups); + + $controller = new ContentType($contentTypeService); + + $result = $controller->loadContentTypeGroupList(new Request()); + + self::assertInstanceOf(ContentTypeGroupList::class, $result); + self::assertSame($contentTypeGroups, $result->contentTypeGroups); + } + + public function testLoadContentTypeGroupListIncludesSystemWhenRequested(): void + { + $contentTypeService = $this->createMock(ContentTypeService::class); + $contentTypeGroups = [$this->createMock(ContentTypeGroup::class)]; + + $contentTypeService + ->expects(self::once()) + ->method('loadContentTypeGroups') + ->with(Language::ALL, true) + ->willReturn($contentTypeGroups); + + $controller = new ContentType($contentTypeService); + + $result = $controller->loadContentTypeGroupList(new Request(['includeSystem' => 'true'])); + + self::assertInstanceOf(ContentTypeGroupList::class, $result); + self::assertSame($contentTypeGroups, $result->contentTypeGroups); + } +} From 76e34151c7ef73b87aa8bbc00afa4fb74891f27d Mon Sep 17 00:00:00 2001 From: mikolaj Date: Fri, 28 Nov 2025 17:28:04 +0100 Subject: [PATCH 02/11] Updated ibexa/core version to dev-ct-group-is-system as 4.6.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4b9e8699e..56aa50e41 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "ext-libxml": "*", "ext-simplexml": "*", "ext-xmlwriter": "*", - "ibexa/core": "~4.6.0@dev", + "ibexa/core": "dev-ct-group-is-system as 4.6.x-dev", "symfony/http-kernel": "^5.3", "symfony/dependency-injection": "^5.3", "symfony/routing": "^5.3", From a4cf5b2e5e9c49be8a1fa4fa6c71e2cbb1dfb777 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Fri, 28 Nov 2025 17:42:23 +0100 Subject: [PATCH 03/11] Fixed LogicalNot parser validation for NOT key and criterion name --- src/lib/Server/Input/Parser/Criterion/LogicalNot.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/Server/Input/Parser/Criterion/LogicalNot.php b/src/lib/Server/Input/Parser/Criterion/LogicalNot.php index 5bf03b787..8891b4485 100644 --- a/src/lib/Server/Input/Parser/Criterion/LogicalNot.php +++ b/src/lib/Server/Input/Parser/Criterion/LogicalNot.php @@ -28,7 +28,7 @@ class LogicalNot extends CriterionParser */ public function parse(array $data, ParsingDispatcher $parsingDispatcher) { - if (!array_key_exists('NOT', $data) && !is_array($data['NOT'])) { + if (!array_key_exists('NOT', $data) || !is_array($data['NOT'])) { throw new Exceptions\Parser('Invalid format'); } @@ -37,6 +37,11 @@ public function parse(array $data, ParsingDispatcher $parsingDispatcher) } $criterionName = key($data['NOT']); $criterionData = current($data['NOT']); + + if (!is_string($criterionName)) { + throw new Exceptions\Parser('Invalid format'); + } + $criteria = $this->dispatchCriterion($criterionName, $criterionData, $parsingDispatcher); return new LogicalNotCriterion($criteria); From fcdac5edbc41ec7b8537fd6d4b1acc8bf2231a97 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 11:28:45 +0100 Subject: [PATCH 04/11] Refactored CI configuration to use Ibexa's composer-install action and streamline steps --- .github/workflows/ci.yaml | 58 ++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d7639f10b..b229718ca 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,22 +12,17 @@ jobs: name: Run code style check runs-on: "ubuntu-24.04" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup PHP Action - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - coverage: none - extensions: 'pdo_sqlite, gd' - tools: cs2pr + - uses: ibexa/gh-workflows/actions/composer-install@main + with: + gh-client-id: ${{ secrets.AUTOMATION_CLIENT_ID }} + gh-client-secret: ${{ secrets.AUTOMATION_CLIENT_SECRET }} + satis-network-key: ${{ secrets.SATIS_NETWORK_KEY }} + satis-network-token: ${{ secrets.SATIS_NETWORK_TOKEN }} - - uses: ramsey/composer-install@v3 - with: - dependency-versions: highest - - - name: Run code style check - run: composer run-script check-cs -- --format=checkstyle | cs2pr + - name: Run code style check + run: composer run-script check-cs -- --format=checkstyle | cs2pr tests: name: Unit & integration tests @@ -43,30 +38,25 @@ jobs: - '8.2' steps: - - uses: actions/checkout@v4 - - - name: Setup PHP Action - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: none - extensions: pdo_sqlite, gd - tools: cs2pr + - uses: actions/checkout@v4 - - uses: ramsey/composer-install@v3 - with: - dependency-versions: highest + - uses: ibexa/gh-workflows/actions/composer-install@main + with: + gh-client-id: ${{ secrets.AUTOMATION_CLIENT_ID }} + gh-client-secret: ${{ secrets.AUTOMATION_CLIENT_SECRET }} + satis-network-key: ${{ secrets.SATIS_NETWORK_KEY }} + satis-network-token: ${{ secrets.SATIS_NETWORK_TOKEN }} - - name: Run PHPStan analysis - run: composer run-script phpstan + - name: Run PHPStan analysis + run: composer run-script phpstan - - name: Setup problem matchers for PHPUnit - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Run unit test suite - run: composer test - - name: Run integration test suite - run: composer test-integration + - name: Run unit test suite + run: composer test + - name: Run integration test suite + run: composer test-integration functional-tests: name: "REST functional tests" From 6648427d8d7d1e18f55ba27a5d51a3b3e6e96e87 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 11:28:57 +0100 Subject: [PATCH 05/11] Added tests for content type group list API endpoints --- tests/bundle/Functional/ContentTypeTest.php | 62 +++++++++++++++++++ .../Controller/ContentTypeControllerTest.php | 56 ----------------- 2 files changed, 62 insertions(+), 56 deletions(-) delete mode 100644 tests/lib/Server/Controller/ContentTypeControllerTest.php diff --git a/tests/bundle/Functional/ContentTypeTest.php b/tests/bundle/Functional/ContentTypeTest.php index b41027a28..304c5a3dd 100644 --- a/tests/bundle/Functional/ContentTypeTest.php +++ b/tests/bundle/Functional/ContentTypeTest.php @@ -7,6 +7,7 @@ namespace Ibexa\Tests\Bundle\Rest\Functional; use Ibexa\Tests\Bundle\Rest\Functional\TestCase as RESTFunctionalTestCase; +use Psr\Http\Message\ResponseInterface; class ContentTypeTest extends RESTFunctionalTestCase { @@ -159,6 +160,46 @@ public function testLoadContentTypeGroupList() // @todo test data } + /** + * Covers GET /content/typegroups without includeSystem param (defaults to false). + */ + public function testLoadContentTypeGroupListExcludesSystemByDefault(): void + { + $response = $this->sendHttpRequest( + $this->createHttpRequest( + 'GET', + '/api/ibexa/v2/content/typegroups', + '', + 'ContentTypeGroupList+json' + ) + ); + self::assertHttpResponseCodeEquals($response, 200); + + $identifiers = $this->getContentTypeGroupIdentifiers($response); + + self::assertNotContains('users', $identifiers); + } + + /** + * Covers GET /content/typegroups?includeSystem=true. + */ + public function testLoadContentTypeGroupListIncludesSystemGroups(): void + { + $response = $this->sendHttpRequest( + $this->createHttpRequest( + 'GET', + '/api/ibexa/v2/content/typegroups?includeSystem=true', + '', + 'ContentTypeGroupList+json' + ) + ); + self::assertHttpResponseCodeEquals($response, 200); + + $identifiers = $this->getContentTypeGroupIdentifiers($response); + + self::assertContains('users', $identifiers); + } + /** * @depends testUpdateContentTypeGroup * Covers GET /content/typegroups?identifier= @@ -672,6 +713,27 @@ public function testCreateViewWithContentTypeGroupName(): void self::assertArrayHasKey('ContentTypeList', $responseData); self::assertSame('image', $responseData['ContentTypeList']['ContentType'][0]['identifier']); } + + /** + * @return string[] + */ + private function getContentTypeGroupIdentifiers(ResponseInterface $response): array + { + $responseData = json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR); + self::assertArrayHasKey('ContentTypeGroupList', $responseData); + + $groupList = $responseData['ContentTypeGroupList']['ContentTypeGroup'] ?? []; + if (isset($groupList['_href'])) { + $groupList = [$groupList]; + } + + return array_map( + static function (array $group): string { + return strtolower($group['identifier'] ?? ''); + }, + $groupList + ); + } } class_alias(ContentTypeTest::class, 'EzSystems\EzPlatformRestBundle\Tests\Functional\ContentTypeTest'); diff --git a/tests/lib/Server/Controller/ContentTypeControllerTest.php b/tests/lib/Server/Controller/ContentTypeControllerTest.php deleted file mode 100644 index bb59fcd43..000000000 --- a/tests/lib/Server/Controller/ContentTypeControllerTest.php +++ /dev/null @@ -1,56 +0,0 @@ -createMock(ContentTypeService::class); - $contentTypeGroups = [$this->createMock(ContentTypeGroup::class)]; - - $contentTypeService - ->expects(self::once()) - ->method('loadContentTypeGroups') - ->with(Language::ALL, false) - ->willReturn($contentTypeGroups); - - $controller = new ContentType($contentTypeService); - - $result = $controller->loadContentTypeGroupList(new Request()); - - self::assertInstanceOf(ContentTypeGroupList::class, $result); - self::assertSame($contentTypeGroups, $result->contentTypeGroups); - } - - public function testLoadContentTypeGroupListIncludesSystemWhenRequested(): void - { - $contentTypeService = $this->createMock(ContentTypeService::class); - $contentTypeGroups = [$this->createMock(ContentTypeGroup::class)]; - - $contentTypeService - ->expects(self::once()) - ->method('loadContentTypeGroups') - ->with(Language::ALL, true) - ->willReturn($contentTypeGroups); - - $controller = new ContentType($contentTypeService); - - $result = $controller->loadContentTypeGroupList(new Request(['includeSystem' => 'true'])); - - self::assertInstanceOf(ContentTypeGroupList::class, $result); - self::assertSame($contentTypeGroups, $result->contentTypeGroups); - } -} From c2b74fe77dc5b40428220e2df4b903b2f1e5457c Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 11:29:32 +0100 Subject: [PATCH 06/11] [TMP] Added dependencies.json for Ibexa core package configuration --- dependencies.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 dependencies.json diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 000000000..fc4a24d50 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,11 @@ +{ + "recipesEndpoint": "", + "packages": [ + { + "requirement": "dev-ct-group-is-system as 4.6.x-dev", + "repositoryUrl": "https://github.com/ibexa/core", + "package": "ibexa/core", + "shouldBeAddedAsVCS": false + } + ] +} \ No newline at end of file From fac6f2b3e3af714dbe885817324a295db27b5814 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 11:35:32 +0100 Subject: [PATCH 07/11] Updated ibexa/core dependency version to ~4.6.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 56aa50e41..41ab6c1ff 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "ext-libxml": "*", "ext-simplexml": "*", "ext-xmlwriter": "*", - "ibexa/core": "dev-ct-group-is-system as 4.6.x-dev", + "ibexa/core": "~4.6.x-dev", "symfony/http-kernel": "^5.3", "symfony/dependency-injection": "^5.3", "symfony/routing": "^5.3", From f819c11c872cea16380ba12fdf71afebc538f7a1 Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 12:57:42 +0100 Subject: [PATCH 08/11] Added PHP version matrix to CI configuration --- .github/workflows/ci.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b229718ca..fd457a880 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,6 +11,15 @@ jobs: cs-fix: name: Run code style check runs-on: "ubuntu-24.04" + + strategy: + fail-fast: false + matrix: + php: + - '7.4' + - '8.0' + - '8.2' + steps: - uses: actions/checkout@v4 From 1356aca9aa6304441683d3b396a1b3d0444d398a Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 15:26:12 +0100 Subject: [PATCH 09/11] Updated ContentTypeTest assertions for identifier checks --- tests/bundle/Functional/ContentTypeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bundle/Functional/ContentTypeTest.php b/tests/bundle/Functional/ContentTypeTest.php index 304c5a3dd..287833ab1 100644 --- a/tests/bundle/Functional/ContentTypeTest.php +++ b/tests/bundle/Functional/ContentTypeTest.php @@ -177,7 +177,7 @@ public function testLoadContentTypeGroupListExcludesSystemByDefault(): void $identifiers = $this->getContentTypeGroupIdentifiers($response); - self::assertNotContains('users', $identifiers); + self::assertNotContains('system', $identifiers); } /** @@ -197,7 +197,7 @@ public function testLoadContentTypeGroupListIncludesSystemGroups(): void $identifiers = $this->getContentTypeGroupIdentifiers($response); - self::assertContains('users', $identifiers); + self::assertContains('system', $identifiers); } /** From 1aee655470a260675f4fbfd6ea7eb870e77dc7af Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 16:46:39 +0100 Subject: [PATCH 10/11] Updated CI configuration to use PHP 8.1 --- .github/workflows/ci.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fd457a880..83fc34367 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,8 +17,7 @@ jobs: matrix: php: - '7.4' - - '8.0' - - '8.2' + - '8.1' steps: - uses: actions/checkout@v4 @@ -43,8 +42,7 @@ jobs: matrix: php: - '7.4' - - '8.0' - - '8.2' + - '8.1' steps: - uses: actions/checkout@v4 From 357697ee161add0be48ea6e56571fca43462cb8a Mon Sep 17 00:00:00 2001 From: mikolaj Date: Mon, 1 Dec 2025 16:50:04 +0100 Subject: [PATCH 11/11] Add isSystem property to ContentTypeGroupCreateStruct --- .../Input/Parser/ContentTypeGroupInput.php | 4 ++ tests/bundle/Functional/ContentTypeTest.php | 55 ++++++++++++++++++- .../Parser/ContentTypeGroupInputTest.php | 6 ++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Input/Parser/ContentTypeGroupInput.php b/src/lib/Server/Input/Parser/ContentTypeGroupInput.php index 1860e2b16..854bbadf5 100644 --- a/src/lib/Server/Input/Parser/ContentTypeGroupInput.php +++ b/src/lib/Server/Input/Parser/ContentTypeGroupInput.php @@ -68,6 +68,10 @@ public function parse(array $data, ParsingDispatcher $parsingDispatcher) $contentTypeGroupCreateStruct->creationDate = new DateTime($data['modificationDate']); } + if (array_key_exists('isSystem', $data)) { + $contentTypeGroupCreateStruct->isSystem = $this->parserTools->parseBooleanValue($data['isSystem']); + } + // @todo mainLanguageCode, names, descriptions? if (array_key_exists('User', $data) && is_array($data['User'])) { diff --git a/tests/bundle/Functional/ContentTypeTest.php b/tests/bundle/Functional/ContentTypeTest.php index 287833ab1..dd0903694 100644 --- a/tests/bundle/Functional/ContentTypeTest.php +++ b/tests/bundle/Functional/ContentTypeTest.php @@ -165,6 +165,8 @@ public function testLoadContentTypeGroupList() */ public function testLoadContentTypeGroupListExcludesSystemByDefault(): void { + $systemIdentifier = $this->createSystemContentTypeGroup(); + $response = $this->sendHttpRequest( $this->createHttpRequest( 'GET', @@ -177,7 +179,7 @@ public function testLoadContentTypeGroupListExcludesSystemByDefault(): void $identifiers = $this->getContentTypeGroupIdentifiers($response); - self::assertNotContains('system', $identifiers); + self::assertNotContains($systemIdentifier, $identifiers); } /** @@ -185,6 +187,8 @@ public function testLoadContentTypeGroupListExcludesSystemByDefault(): void */ public function testLoadContentTypeGroupListIncludesSystemGroups(): void { + $systemIdentifier = $this->createSystemContentTypeGroup(); + $response = $this->sendHttpRequest( $this->createHttpRequest( 'GET', @@ -197,7 +201,7 @@ public function testLoadContentTypeGroupListIncludesSystemGroups(): void $identifiers = $this->getContentTypeGroupIdentifiers($response); - self::assertContains('system', $identifiers); + self::assertContains($systemIdentifier, $identifiers); } /** @@ -213,6 +217,25 @@ public function testLoadContentTypeGroupListWithIdentifier() self::assertHttpResponseCodeEquals($response, 307); } + public function testCreateSystemContentTypeGroup(): void + { + $identifier = $this->createSystemContentTypeGroup(); + + $response = $this->sendHttpRequest( + $this->createHttpRequest( + 'GET', + '/api/ibexa/v2/content/typegroups?includeSystem=true', + '', + 'ContentTypeGroupList+json' + ) + ); + self::assertHttpResponseCodeEquals($response, 200); + + $identifiers = $this->getContentTypeGroupIdentifiers($response); + + self::assertContains($identifier, $identifiers); + } + /** * @depends testUpdateContentTypeGroup * Covers GET /content/typegroups/ @@ -734,6 +757,34 @@ static function (array $group): string { $groupList ); } + + private function createSystemContentTypeGroup(): string + { + $identifier = $this->addTestSuffix('system_group_' . uniqid()); + $body = <<< XML + + + {$identifier} + true + +XML; + + $request = $this->createHttpRequest( + 'POST', + '/api/ibexa/v2/content/typegroups', + 'ContentTypeGroupInput+xml', + 'ContentTypeGroup+json', + $body + ); + + $response = $this->sendHttpRequest($request); + self::assertHttpResponseCodeEquals($response, 201); + self::assertHttpResponseHasHeader($response, 'Location'); + + $this->addCreatedElement($response->getHeader('Location')[0]); + + return $identifier; + } } class_alias(ContentTypeTest::class, 'EzSystems\EzPlatformRestBundle\Tests\Functional\ContentTypeTest'); diff --git a/tests/lib/Server/Input/Parser/ContentTypeGroupInputTest.php b/tests/lib/Server/Input/Parser/ContentTypeGroupInputTest.php index 1f2bac970..44c767433 100644 --- a/tests/lib/Server/Input/Parser/ContentTypeGroupInputTest.php +++ b/tests/lib/Server/Input/Parser/ContentTypeGroupInputTest.php @@ -24,6 +24,7 @@ public function testParse() '_href' => '/user/users/14', ], 'modificationDate' => '2012-12-31T12:00:00', + 'isSystem' => true, ]; $contentTypeGroupInput = $this->getParser(); @@ -52,6 +53,11 @@ public function testParse() $result->creationDate, 'ContentTypeGroupCreateStruct creationDate property not created correctly.' ); + + $this->assertTrue( + $result->isSystem, + 'ContentTypeGroupCreateStruct isSystem property not created correctly.' + ); } /**