From a427eae5b8a217dc7ca5dd38ac019b2860616f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 23 Mar 2026 15:37:12 +0100 Subject: [PATCH 1/7] Added assertions to LanguageCreateStruct --- .../Values/Content/LanguageCreateStruct.php | 3 + src/contracts/Validation/StructValidator.php | 10 +- src/lib/Repository/LanguageService.php | 14 +- src/lib/Repository/Repository.php | 4 +- .../Core/Repository/LanguageServiceTest.php | 477 +++++------------- 5 files changed, 137 insertions(+), 371 deletions(-) diff --git a/src/contracts/Repository/Values/Content/LanguageCreateStruct.php b/src/contracts/Repository/Values/Content/LanguageCreateStruct.php index 84d291aa92..30bed7ea95 100644 --- a/src/contracts/Repository/Values/Content/LanguageCreateStruct.php +++ b/src/contracts/Repository/Values/Content/LanguageCreateStruct.php @@ -9,6 +9,7 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content; use Ibexa\Contracts\Core\Repository\Values\ValueObject; +use Symfony\Component\Validator\Constraints as Assert; /** * This class represents a value for creating a language. @@ -20,6 +21,8 @@ class LanguageCreateStruct extends ValueObject * * Needs to be a unique. */ + #[Assert\NotBlank] + #[Assert\Regex('^[a-zA-Z\_\-]+$')] public ?string $languageCode = null; /** diff --git a/src/contracts/Validation/StructValidator.php b/src/contracts/Validation/StructValidator.php index b0073afbf4..85bfe8b4fd 100644 --- a/src/contracts/Validation/StructValidator.php +++ b/src/contracts/Validation/StructValidator.php @@ -10,13 +10,11 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; -final class StructValidator +final readonly class StructValidator { - private ValidatorInterface $validator; - - public function __construct(ValidatorInterface $validator) - { - $this->validator = $validator; + public function __construct( + private ValidatorInterface $validator + ) { } /** diff --git a/src/lib/Repository/LanguageService.php b/src/lib/Repository/LanguageService.php index efaa763a41..aefe199f1d 100644 --- a/src/lib/Repository/LanguageService.php +++ b/src/lib/Repository/LanguageService.php @@ -17,6 +17,7 @@ use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface; use Ibexa\Contracts\Core\Repository\Values\Content\Language; use Ibexa\Contracts\Core\Repository\Values\Content\LanguageCreateStruct; +use Ibexa\Contracts\Core\Validation\StructValidator; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Base\Exceptions\InvalidArgumentValue; use Ibexa\Core\Base\Exceptions\UnauthorizedException; @@ -36,9 +37,6 @@ class LanguageService implements LanguageServiceInterface /** @var array */ protected $settings; - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ - private $permissionResolver; - /** * Setups service with reference to repository object that created it & corresponding handler. * @@ -49,12 +47,12 @@ class LanguageService implements LanguageServiceInterface public function __construct( RepositoryInterface $repository, Handler $languageHandler, - PermissionResolver $permissionResolver, - array $settings = [] + private readonly PermissionResolver $permissionResolver, + private readonly StructValidator $validator, + array $settings = [], ) { $this->repository = $repository; $this->languageHandler = $languageHandler; - $this->permissionResolver = $permissionResolver; // Union makes sure default settings are ignored if provided in argument $this->settings = $settings + [ 'languages' => ['eng-GB'], @@ -92,7 +90,7 @@ public function createLanguage(LanguageCreateStruct $languageCreateStruct): Lang sprintf('language with the "%s" language code already exists', $languageCreateStruct->languageCode) ); } - } catch (APINotFoundException $e) { + } catch (APINotFoundException) { // Do nothing } @@ -104,6 +102,8 @@ public function createLanguage(LanguageCreateStruct $languageCreateStruct): Lang ] ); + $this->validator->assertValidStruct('$languageCreateStruct', $createStruct, ['create']); + $this->repository->beginTransaction(); try { $createdLanguage = $this->languageHandler->create($createStruct); diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index e5092d9d2d..2087445c13 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -40,6 +40,7 @@ use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; use Ibexa\Contracts\Core\Search\Handler as SearchHandler; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Ibexa\Contracts\Core\Validation\StructValidator; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Collector\ContentCollector; use Ibexa\Core\Repository\Helper\RelationProcessor; @@ -399,7 +400,8 @@ public function getContentLanguageService(): LanguageServiceInterface $this, $this->persistenceHandler->contentLanguageHandler(), $this->getPermissionResolver(), - $this->serviceSettings['language'] + new StructValidator($this->validator), + $this->serviceSettings['language'], ); return $this->languageService; diff --git a/tests/integration/Core/Repository/LanguageServiceTest.php b/tests/integration/Core/Repository/LanguageServiceTest.php index 3226bce22e..4d26df94a6 100644 --- a/tests/integration/Core/Repository/LanguageServiceTest.php +++ b/tests/integration/Core/Repository/LanguageServiceTest.php @@ -10,8 +10,9 @@ use Exception; use Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException; +use Ibexa\Contracts\Core\Repository\LanguageService; +use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Values\Content\Language; -use Ibexa\Contracts\Core\Repository\Values\Content\LanguageCreateStruct; /** * Test case for operations in the LanguageService using in memory storage. @@ -21,27 +22,23 @@ * @group integration * @group language */ -class LanguageServiceTest extends BaseTestCase +final class LanguageServiceTest extends BaseTestCase { - /** - * Test for the newLanguageCreateStruct() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::newLanguageCreateStruct - */ - public function testNewLanguageCreateStruct() - { - $repository = $this->getRepository(); + private Repository $repository; - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); + private LanguageService $languageService; - $languageCreate = $languageService->newLanguageCreateStruct(); - /* END: Use Case */ + protected function setUp(): void + { + parent::setUp(); - self::assertInstanceOf( - LanguageCreateStruct::class, - $languageCreate - ); + $this->repository = $this->getRepository(); + $this->languageService = $this->repository->getContentLanguageService(); + } + + public function testNewLanguageCreateStruct(): void + { + $languageCreate = $this->languageService->newLanguageCreateStruct(); $this->assertPropertiesCorrect( [ @@ -54,61 +51,34 @@ public function testNewLanguageCreateStruct() } /** - * Test for the createLanguage() method. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Language - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testNewLanguageCreateStruct */ - public function testCreateLanguage() + public function testCreateLanguage(): Language { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English (New Zealand)'; $languageCreate->languageCode = 'eng-NZ'; - $language = $languageService->createLanguage($languageCreate); - /* END: Use Case */ + $language = $this->languageService->createLanguage($languageCreate); - self::assertInstanceOf( - Language::class, - $language - ); + $this->expectNotToPerformAssertions(); return $language; } /** - * Test for the createLanguage() method. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Language $language - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testCreateLanguage */ - public function testCreateLanguageSetsIdPropertyOnReturnedLanguage($language) + public function testCreateLanguageSetsIdPropertyOnReturnedLanguage(Language $language): void { self::assertNotNull($language->id); } /** - * Test for the createLanguage() method. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Language $language - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testCreateLanguage */ - public function testCreateLanguageSetsExpectedProperties($language) + public function testCreateLanguageSetsExpectedProperties(Language $language): void { self::assertEquals( [ @@ -125,64 +95,39 @@ public function testCreateLanguageSetsExpectedProperties($language) } /** - * Test for the createLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testCreateLanguage */ - public function testCreateLanguageThrowsInvalidArgumentException() + public function testCreateLanguageThrowsInvalidArgumentException(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Argument \'languageCreateStruct\' is invalid: language with the "nor-NO" language code already exists'); - - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'Norwegian'; $languageCreate->languageCode = 'nor-NO'; - $languageService->createLanguage($languageCreate); + $this->languageService->createLanguage($languageCreate); // This call should fail with an InvalidArgumentException, because // the language code "nor-NO" already exists. - $languageService->createLanguage($languageCreate); - /* END: Use Case */ + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Argument \'languageCreateStruct\' is invalid: language with the "nor-NO" language code already exists'); + $this->languageService->createLanguage($languageCreate); } /** - * Test for the loadLanguageById() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageById - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageListById - * * @depends testCreateLanguage */ - public function testLoadLanguageById() + public function testLoadLanguageById(): void { - $repository = $this->getRepository(); - - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = false; $languageCreate->name = 'English'; $languageCreate->languageCode = 'eng-NZ'; - $languageId = $languageService->createLanguage($languageCreate)->id; - - $language = $languageService->loadLanguageById($languageId); + $languageId = $this->languageService->createLanguage($languageCreate)->id; - self::assertInstanceOf( - Language::class, - $language - ); + $this->languageService->loadLanguageById($languageId); - $languages = iterator_to_array($languageService->loadLanguageListById([$languageId])); + $languages = iterator_to_array($this->languageService->loadLanguageListById([$languageId])); self::assertIsIterable($languages); self::assertCount(1, $languages); @@ -190,69 +135,43 @@ public function testLoadLanguageById() } /** - * Test for the loadLanguageById() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageById - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageListById - * * @depends testLoadLanguageById */ - public function testLoadLanguageByIdThrowsNotFoundException() + public function testLoadLanguageByIdThrowsNotFoundException(): void { - $repository = $this->getRepository(); - $nonExistentLanguageId = $this->generateId('language', 2342); - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - $languages = $languageService->loadLanguageListById([$nonExistentLanguageId]); + $languages = $this->languageService->loadLanguageListById([$nonExistentLanguageId]); self::assertIsIterable($languages); self::assertCount(0, $languages); $this->expectException(NotFoundException::class); - $languageService->loadLanguageById($nonExistentLanguageId); - /* END: Use Case */ + $this->languageService->loadLanguageById($nonExistentLanguageId); } /** - * Test for the updateLanguageName() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::updateLanguageName - * * @depends testLoadLanguageById */ - public function testUpdateLanguageName() + public function testUpdateLanguageName(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = false; $languageCreate->name = 'English'; $languageCreate->languageCode = 'eng-NZ'; - $languageId = $languageService->createLanguage($languageCreate)->id; + $languageId = $this->languageService->createLanguage($languageCreate)->id; - $language = $languageService->loadLanguageById($languageId); + $language = $this->languageService->loadLanguageById($languageId); - $updatedLanguage = $languageService->updateLanguageName( + $this->languageService->updateLanguageName( $language, 'New language name.' ); - /* END: Use Case */ - - // Verify that the service returns an updated language instance. - self::assertInstanceOf( - Language::class, - $updatedLanguage - ); // Verify that the service also persists the changes - $updatedLanguage = $languageService->loadLanguageById($languageId); + $updatedLanguage = $this->languageService->loadLanguageById($languageId); $this->assertPropertiesCorrect( [ 'id' => $language->id, @@ -264,108 +183,68 @@ public function testUpdateLanguageName() ); } - /** - * Test service method for updating language name throwing InvalidArgumentException. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::updateLanguageName - */ - public function testUpdateLanguageNameThrowsInvalidArgumentException() + public function testUpdateLanguageNameThrowsInvalidArgumentException(): void { + $language = $this->languageService->loadLanguage('eng-GB'); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Argument \'newName\' is invalid: \'\' is incorrect value'); - - $repository = $this->getRepository(); - $languageService = $repository->getContentLanguageService(); - - $language = $languageService->loadLanguage('eng-GB'); - $languageService->updateLanguageName($language, ''); + $this->languageService->updateLanguageName($language, ''); } /** - * Test for the enableLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::enableLanguage - * * @depends testLoadLanguageById */ - public function testEnableLanguage() + public function testEnableLanguage(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = false; $languageCreate->name = 'English'; $languageCreate->languageCode = 'eng-NZ'; - $language = $languageService->createLanguage($languageCreate); + $language = $this->languageService->createLanguage($languageCreate); // Now lets enable the newly created language - $languageService->enableLanguage($language); + $this->languageService->enableLanguage($language); - $enabledLanguage = $languageService->loadLanguageById($language->id); - /* END: Use Case */ + $enabledLanguage = $this->languageService->loadLanguageById($language->id); self::assertTrue($enabledLanguage->enabled); } /** - * Test for the disableLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::disableLanguage - * * @depends testLoadLanguageById */ - public function testDisableLanguage() + public function testDisableLanguage(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English'; $languageCreate->languageCode = 'eng-NZ'; - $language = $languageService->createLanguage($languageCreate); + $language = $this->languageService->createLanguage($languageCreate); // Now lets disable the newly created language - $languageService->disableLanguage($language); + $this->languageService->disableLanguage($language); - $enabledLanguage = $languageService->loadLanguageById($language->id); - /* END: Use Case */ + $enabledLanguage = $this->languageService->loadLanguageById($language->id); self::assertFalse($enabledLanguage->enabled); } /** - * Test for the loadLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguage - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageListByCode - * * @depends testCreateLanguage */ - public function testLoadLanguage() + public function testLoadLanguage(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English'; $languageCreate->languageCode = 'eng-NZ'; - $languageId = $languageService->createLanguage($languageCreate)->id; + $languageId = $this->languageService->createLanguage($languageCreate)->id; - // Now load the newly created language by it's language code - $language = $languageService->loadLanguage('eng-NZ'); - /* END: Use Case */ + $language = $this->languageService->loadLanguage('eng-NZ'); $this->assertPropertiesCorrect( [ @@ -377,7 +256,7 @@ public function testLoadLanguage() $language ); - $languages = iterator_to_array($languageService->loadLanguageListByCode(['eng-NZ'])); + $languages = iterator_to_array($this->languageService->loadLanguageListByCode(['eng-NZ'])); self::assertIsIterable($languages); self::assertCount(1, $languages); @@ -394,178 +273,120 @@ public function testLoadLanguage() } /** - * Test for the loadLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguage - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguageListByCode - * * @depends testLoadLanguage */ - public function testLoadLanguageThrowsNotFoundException() + public function testLoadLanguageThrowsNotFoundException(): void { - $repository = $this->getRepository(); - - $languageService = $repository->getContentLanguageService(); - - $languages = $languageService->loadLanguageListByCode(['fre-FR']); + $languages = $this->languageService->loadLanguageListByCode(['fre-FR']); self::assertIsIterable($languages); self::assertCount(0, $languages); $this->expectException(NotFoundException::class); - $languageService->loadLanguage('fre-FR'); + $this->languageService->loadLanguage('fre-FR'); } - /** - * Test service method for loading language throwing InvalidArgumentException. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguage - */ - public function testLoadLanguageThrowsInvalidArgumentException() + public function testLoadLanguageThrowsInvalidArgumentException(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Argument \'languageCode\' is invalid: language code has an invalid value'); - $repository = $this->getRepository(); - - $repository->getContentLanguageService()->loadLanguage(''); + $this->languageService->loadLanguage(''); } /** - * Test for the loadLanguages() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguages - * * @depends testCreateLanguage * @depends testLoadLanguage */ - public function testLoadLanguages() + public function testLoadLanguages(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - // Create some languages - $languageCreateEnglish = $languageService->newLanguageCreateStruct(); + $languageCreateEnglish = $this->languageService->newLanguageCreateStruct(); $languageCreateEnglish->enabled = false; $languageCreateEnglish->name = 'English'; $languageCreateEnglish->languageCode = 'eng-NZ'; - $languageCreateFrench = $languageService->newLanguageCreateStruct(); + $languageCreateFrench = $this->languageService->newLanguageCreateStruct(); $languageCreateFrench->enabled = false; $languageCreateFrench->name = 'French'; $languageCreateFrench->languageCode = 'fre-FR'; - $languageService->createLanguage($languageCreateEnglish); - $languageService->createLanguage($languageCreateFrench); + $this->languageService->createLanguage($languageCreateEnglish); + $this->languageService->createLanguage($languageCreateFrench); - $languages = $languageService->loadLanguages(); + $languages = $this->languageService->loadLanguages(); self::assertIsArray($languages); foreach ($languages as $language) { self::assertInstanceOf(Language::class, $language); - $singleLanguage = $languageService->loadLanguage($language->languageCode); + $singleLanguage = $this->languageService->loadLanguage($language->languageCode); $this->assertStructPropertiesCorrect( $singleLanguage, $language, ['id', 'languageCode', 'name', 'enabled'] ); } - /* END: Use Case */ // eng-US, eng-GB, ger-DE + 2 newly created self::assertCount(5, $languages); } /** - * Test for the loadLanguages() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::loadLanguages - * * @depends testCreateLanguage */ - public function loadLanguagesReturnsAnEmptyArrayByDefault() + public function loadLanguagesReturnsAnEmptyArrayByDefault(): void { - $repository = $this->getRepository(); - - $languageService = $repository->getContentLanguageService(); - - self::assertSame([], $languageService->loadLanguages()); + self::assertSame([], $this->languageService->loadLanguages()); } /** - * Test for the deleteLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::deleteLanguage - * * @depends testLoadLanguages */ - public function testDeleteLanguage() + public function testDeleteLanguage(): void { - $repository = $this->getRepository(); - $languageService = $repository->getContentLanguageService(); + $beforeCount = count($this->languageService->loadLanguages()); - $beforeCount = count($languageService->loadLanguages()); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - $languageCreateEnglish = $languageService->newLanguageCreateStruct(); + $languageCreateEnglish = $this->languageService->newLanguageCreateStruct(); $languageCreateEnglish->enabled = false; $languageCreateEnglish->name = 'English'; $languageCreateEnglish->languageCode = 'eng-NZ'; - $language = $languageService->createLanguage($languageCreateEnglish); + $language = $this->languageService->createLanguage($languageCreateEnglish); // Delete the newly created language - $languageService->deleteLanguage($language); - /* END: Use Case */ + $this->languageService->deleteLanguage($language); // +1 -1 - self::assertEquals($beforeCount, count($languageService->loadLanguages())); + self::assertEquals($beforeCount, count($this->languageService->loadLanguages())); $this->expectException(NotFoundException::class); $this->expectExceptionMessage('Could not find \'Language\' with identifier \'eng-NZ\''); // ensure just created & deleted language doesn't exist - $languageService->loadLanguage($languageCreateEnglish->languageCode); - self::fail('Language is still returned after being deleted'); + $this->languageService->loadLanguage($languageCreateEnglish->languageCode); } /** - * Test for the deleteLanguage() method. - * * NOTE: This test has a dependency against several methods in the content * service, but because there is no topological sort for test dependencies * we cannot declare them here. * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::deleteLanguage - * * @depends testDeleteLanguage */ - public function testDeleteLanguageThrowsInvalidArgumentException() + public function testDeleteLanguageThrowsInvalidArgumentException(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Argument \'language\' is invalid: Cannot delete language: some content still references the language'); - - $repository = $this->getRepository(); - $editorsGroupId = $this->generateId('group', 13); - /* BEGIN: Use Case */ // $editorsGroupId is the ID of the "Editors" user group in an Ibexa // Publish demo installation - $languageService = $repository->getContentLanguageService(); - - $languageCreateEnglish = $languageService->newLanguageCreateStruct(); + $languageCreateEnglish = $this->languageService->newLanguageCreateStruct(); $languageCreateEnglish->enabled = true; $languageCreateEnglish->name = 'English'; $languageCreateEnglish->languageCode = 'eng-NZ'; - $language = $languageService->createLanguage($languageCreateEnglish); + $language = $this->languageService->createLanguage($languageCreateEnglish); - $contentService = $repository->getContentService(); + $contentService = $this->repository->getContentService(); // Get metadata update struct and set new language as main language. $metadataUpdate = $contentService->newContentMetadataUpdateStruct(); @@ -579,188 +400,130 @@ public function testDeleteLanguageThrowsInvalidArgumentException() // This call will fail with an "InvalidArgumentException", because the // new language is used by a content object. - $languageService->deleteLanguage($language); - /* END: Use Case */ + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Argument \'language\' is invalid: Cannot delete language: some content still references the language'); + $this->languageService->deleteLanguage($language); } - /** - * Test for the getDefaultLanguageCode() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::getDefaultLanguageCode - */ - public function testGetDefaultLanguageCode() + public function testGetDefaultLanguageCode(): void { - $repository = $this->getRepository(); - $languageService = $repository->getContentLanguageService(); - - self::assertRegExp( - '(^[a-z]{3}\-[A-Z]{2}$)', - $languageService->getDefaultLanguageCode() + self::assertMatchesRegularExpression( + '(^[a-z]{3}-[A-Z]{2}$)', + $this->languageService->getDefaultLanguageCode() ); } /** - * Test for the createLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testCreateLanguage */ - public function testCreateLanguageInTransactionWithRollback() + public function testCreateLanguageInTransactionWithRollback(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - // Start a new transaction - $repository->beginTransaction(); + $this->repository->beginTransaction(); try { // Get create struct and set properties - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English (New Zealand)'; $languageCreate->languageCode = 'eng-NZ'; // Create new language - $languageService->createLanguage($languageCreate); + $this->languageService->createLanguage($languageCreate); } catch (Exception $e) { // Cleanup hanging transaction on error - $repository->rollback(); + $this->repository->rollback(); throw $e; } // Rollback all changes - $repository->rollback(); + $this->repository->rollback(); try { // This call will fail with a "NotFoundException" - $languageService->loadLanguage('eng-NZ'); + $this->languageService->loadLanguage('eng-NZ'); } catch (NotFoundException $e) { // Expected execution path } - /* END: Use Case */ self::assertTrue(isset($e), 'Can still load language after rollback'); } /** - * Test for the createLanguage() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::createLanguage - * * @depends testCreateLanguage */ - public function testCreateLanguageInTransactionWithCommit() + public function testCreateLanguageInTransactionWithCommit(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - // Start a new transaction - $repository->beginTransaction(); + $this->repository->beginTransaction(); try { - // Get create struct and set properties - $languageCreate = $languageService->newLanguageCreateStruct(); + $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English (New Zealand)'; $languageCreate->languageCode = 'eng-NZ'; // Create new language - $languageService->createLanguage($languageCreate); + $this->languageService->createLanguage($languageCreate); // Commit all changes - $repository->commit(); + $this->repository->commit(); } catch (Exception $e) { // Cleanup hanging transaction on error - $repository->rollback(); + $this->repository->rollback(); throw $e; } - // Load new language - $language = $languageService->loadLanguage('eng-NZ'); - /* END: Use Case */ + $language = $this->languageService->loadLanguage('eng-NZ'); self::assertEquals('eng-NZ', $language->languageCode); } /** - * Test for the updateLanguageName() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::updateLanguageName - * * @depends testUpdateLanguageName */ - public function testUpdateLanguageNameInTransactionWithRollback() + public function testUpdateLanguageNameInTransactionWithRollback(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - // Start a new transaction - $repository->beginTransaction(); + $this->repository->beginTransaction(); try { - // Load an existing language - $language = $languageService->loadLanguage('eng-US'); + $language = $this->languageService->loadLanguage('eng-US'); - // Update the language name - $languageService->updateLanguageName($language, 'My English'); + $this->languageService->updateLanguageName($language, 'My English'); } catch (Exception $e) { // Cleanup hanging transaction on error - $repository->rollback(); + $this->repository->rollback(); throw $e; } // Rollback all changes - $repository->rollback(); + $this->repository->rollback(); // Load updated version, name will still be "English (American)" - $updatedLanguage = $languageService->loadLanguage('eng-US'); - /* END: Use Case */ + $updatedLanguage = $this->languageService->loadLanguage('eng-US'); self::assertEquals('English (American)', $updatedLanguage->name); } /** - * Test for the updateLanguageName() method. - * - * @covers \Ibexa\Contracts\Core\Repository\LanguageService::updateLanguageName - * * @depends testUpdateLanguageName */ - public function testUpdateLanguageNameInTransactionWithCommit() + public function testUpdateLanguageNameInTransactionWithCommit(): void { - $repository = $this->getRepository(); - - /* BEGIN: Use Case */ - $languageService = $repository->getContentLanguageService(); - - // Start a new transaction - $repository->beginTransaction(); + $this->repository->beginTransaction(); try { - // Load an existing language - $language = $languageService->loadLanguage('eng-US'); + $language = $this->languageService->loadLanguage('eng-US'); - // Update the language name - $languageService->updateLanguageName($language, 'My English'); + $this->languageService->updateLanguageName($language, 'My English'); - // Commit all changes - $repository->commit(); + $this->repository->commit(); } catch (Exception $e) { - // Cleanup hanging transaction on error - $repository->rollback(); + $this->repository->rollback(); throw $e; } // Load updated version, name will be "My English" - $updatedLanguage = $languageService->loadLanguage('eng-US'); - /* END: Use Case */ + $updatedLanguage = $this->languageService->loadLanguage('eng-US'); self::assertEquals('My English', $updatedLanguage->name); } From ad27abb37bef36a77970852acfe6b92d034a4142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 23 Mar 2026 16:37:51 +0100 Subject: [PATCH 2/7] Refactored `LanguageServiceTest` and added stricter validation for `LanguageCreateStruct` --- phpunit-integration-legacy.xml | 2 +- .../Values/Content/LanguageCreateStruct.php | 5 +- src/lib/Repository/LanguageService.php | 2 + tests/integration/Core/CoreTestKernel.php | 16 +++ .../Core/Repository/AssertPropertiesTrait.php | 128 ++++++++++++++++++ .../Core/Repository/BaseTestCase.php | 116 +--------------- .../Core/Repository/LanguageServiceTest.php | 46 ++++++- .../Core/Resources/services/services.php | 24 ++++ 8 files changed, 219 insertions(+), 120 deletions(-) create mode 100644 tests/integration/Core/CoreTestKernel.php create mode 100644 tests/integration/Core/Repository/AssertPropertiesTrait.php create mode 100644 tests/integration/Core/Resources/services/services.php diff --git a/phpunit-integration-legacy.xml b/phpunit-integration-legacy.xml index ecc8215071..3e716dd6b2 100644 --- a/phpunit-integration-legacy.xml +++ b/phpunit-integration-legacy.xml @@ -12,7 +12,7 @@ - + diff --git a/src/contracts/Repository/Values/Content/LanguageCreateStruct.php b/src/contracts/Repository/Values/Content/LanguageCreateStruct.php index 30bed7ea95..886f3066b7 100644 --- a/src/contracts/Repository/Values/Content/LanguageCreateStruct.php +++ b/src/contracts/Repository/Values/Content/LanguageCreateStruct.php @@ -19,15 +19,16 @@ class LanguageCreateStruct extends ValueObject /** * The languageCode code. * - * Needs to be a unique. + * Needs to be unique. */ #[Assert\NotBlank] - #[Assert\Regex('^[a-zA-Z\_\-]+$')] + #[Assert\Regex('~^[a-zA-Z\_\-]+$~')] public ?string $languageCode = null; /** * Human-readable name of the language. */ + #[Assert\NotBlank] public ?string $name = null; /** diff --git a/src/lib/Repository/LanguageService.php b/src/lib/Repository/LanguageService.php index aefe199f1d..07ec48062e 100644 --- a/src/lib/Repository/LanguageService.php +++ b/src/lib/Repository/LanguageService.php @@ -94,6 +94,8 @@ public function createLanguage(LanguageCreateStruct $languageCreateStruct): Lang // Do nothing } + $this->validator->assertValidStruct('$languageCreateStruct', $languageCreateStruct, ['create']); + $createStruct = new CreateStruct( [ 'languageCode' => $languageCreateStruct->languageCode, diff --git a/tests/integration/Core/CoreTestKernel.php b/tests/integration/Core/CoreTestKernel.php new file mode 100644 index 0000000000..361f58437d --- /dev/null +++ b/tests/integration/Core/CoreTestKernel.php @@ -0,0 +1,16 @@ +load(__DIR__ . '/Resources/services/services.php'); + } +} diff --git a/tests/integration/Core/Repository/AssertPropertiesTrait.php b/tests/integration/Core/Repository/AssertPropertiesTrait.php new file mode 100644 index 0000000000..419f302520 --- /dev/null +++ b/tests/integration/Core/Repository/AssertPropertiesTrait.php @@ -0,0 +1,128 @@ + $propertyValue) { + if ($propertyValue instanceof ValueObject) { + $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + } elseif (is_array($propertyValue)) { + foreach ($propertyValue as $key => $value) { + if ($value instanceof ValueObject) { + $this->assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]); + } else { + $this->assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]); + } + } + } else { + $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); + } + } + } + + /** + * Asserts that properties given in $expectedValues are correctly set in + * $actualObject. + * + * If the property type is array, it will be sorted before comparison. + * + * @TODO: introduced because of randomly failing tests, ref: https://issues.ibexa.co/browse/EZP-21734 + * + * @param mixed[] $expectedValues + * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject $actualObject + */ + protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueObject $actualObject): void + { + foreach ($expectedValues as $propertyName => $propertyValue) { + if ($propertyValue instanceof ValueObject) { + $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + } else { + $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true); + } + } + } + + /** + * Asserts all properties from $expectedValues are correctly set in + * $actualObject. Additional (virtual) properties can be asserted using + * $additionalProperties. + * + * @param array $additionalProperties + */ + protected function assertStructPropertiesCorrect( + ValueObject $expectedValues, + ValueObject $actualObject, + array $additionalProperties = [], + ): void { + foreach ($expectedValues as $propertyName => $propertyValue) { + if ($propertyValue instanceof ValueObject) { + $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + } else { + $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); + } + } + + foreach ($additionalProperties as $propertyName) { + $this->assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName); + } + } + + /** + * @param array $items An array of scalar values + * + * @see \Ibexa\Tests\Integration\Core\Repository\BaseTestCase::assertPropertiesCorrectUnsorted + */ + private function sortItems(array &$items): void + { + $sorter = static function ($a, $b): int { + if (!is_scalar($a) || !is_scalar($b)) { + self::fail('Wrong usage: method ' . __METHOD__ . ' accepts only an array of scalar values'); + } + + return strcmp($a, $b); + }; + usort($items, $sorter); + } + + private function assertPropertiesEqual($propertyName, $expectedValue, $actualValue, $sortArray = false): void + { + if ($expectedValue instanceof \ArrayObject) { + $expectedValue = $expectedValue->getArrayCopy(); + } elseif ($expectedValue instanceof \DateTimeInterface) { + $expectedValue = $expectedValue->format(\DateTime::RFC850); + } + if ($actualValue instanceof \ArrayObject) { + $actualValue = $actualValue->getArrayCopy(); + } elseif ($actualValue instanceof \DateTimeInterface) { + $actualValue = $actualValue->format(\DateTime::RFC850); + } + + if ($sortArray && is_array($actualValue) && is_array($expectedValue)) { + $this->sortItems($actualValue); + $this->sortItems($expectedValue); + } + + self::assertEquals( + $expectedValue, + $actualValue, + sprintf('Object property "%s" incorrect.', $propertyName) + ); + } +} diff --git a/tests/integration/Core/Repository/BaseTestCase.php b/tests/integration/Core/Repository/BaseTestCase.php index 72dd8d9fda..1d9e3319e5 100644 --- a/tests/integration/Core/Repository/BaseTestCase.php +++ b/tests/integration/Core/Repository/BaseTestCase.php @@ -37,6 +37,8 @@ */ abstract class BaseTestCase extends TestCase { + use AssertPropertiesTrait; + /** * Maximum integer number accepted by the different backends. */ @@ -184,120 +186,6 @@ protected function getSetupFactory(): SetupFactory return $this->setupFactory; } - /** - * Asserts that properties given in $expectedValues are correctly set in - * $actualObject. - * - * @param mixed[] $expectedValues - * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject $actualObject - */ - protected function assertPropertiesCorrect(array $expectedValues, ValueObject $actualObject) - { - foreach ($expectedValues as $propertyName => $propertyValue) { - if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); - } elseif (is_array($propertyValue)) { - foreach ($propertyValue as $key => $value) { - if ($value instanceof ValueObject) { - $this->assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]); - } else { - $this->assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]); - } - } - } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); - } - } - } - - /** - * Asserts that properties given in $expectedValues are correctly set in - * $actualObject. - * - * If the property type is array, it will be sorted before comparison. - * - * @TODO: introduced because of randomly failing tests, ref: https://issues.ibexa.co/browse/EZP-21734 - * - * @param mixed[] $expectedValues - * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject $actualObject - */ - protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueObject $actualObject) - { - foreach ($expectedValues as $propertyName => $propertyValue) { - if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); - } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true); - } - } - } - - /** - * Asserts all properties from $expectedValues are correctly set in - * $actualObject. Additional (virtual) properties can be asserted using - * $additionalProperties. - * - * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject $expectedValues - * @param \Ibexa\Contracts\Core\Repository\Values\ValueObject $actualObject - * @param array $propertyNames - */ - protected function assertStructPropertiesCorrect(ValueObject $expectedValues, ValueObject $actualObject, array $additionalProperties = []) - { - foreach ($expectedValues as $propertyName => $propertyValue) { - if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); - } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); - } - } - - foreach ($additionalProperties as $propertyName) { - $this->assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName); - } - } - - /** - * @param array $items An array of scalar values - * - * @see \Ibexa\Tests\Integration\Core\Repository\BaseTestCase::assertPropertiesCorrectUnsorted - */ - private function sortItems(array &$items) - { - $sorter = function ($a, $b): int { - if (!is_scalar($a) || !is_scalar($b)) { - $this->fail('Wrong usage: method ' . __METHOD__ . ' accepts only an array of scalar values'); - } - - return strcmp($a, $b); - }; - usort($items, $sorter); - } - - private function assertPropertiesEqual($propertyName, $expectedValue, $actualValue, $sortArray = false) - { - if ($expectedValue instanceof ArrayObject) { - $expectedValue = $expectedValue->getArrayCopy(); - } elseif ($expectedValue instanceof DateTime) { - $expectedValue = $expectedValue->format(DateTime::RFC850); - } - if ($actualValue instanceof ArrayObject) { - $actualValue = $actualValue->getArrayCopy(); - } elseif ($actualValue instanceof DateTime) { - $actualValue = $actualValue->format(DateTime::RFC850); - } - - if ($sortArray && is_array($actualValue) && is_array($expectedValue)) { - $this->sortItems($actualValue); - $this->sortItems($expectedValue); - } - - self::assertEquals( - $expectedValue, - $actualValue, - sprintf('Object property "%s" incorrect.', $propertyName) - ); - } - /** * Create a user in editor user group. */ diff --git a/tests/integration/Core/Repository/LanguageServiceTest.php b/tests/integration/Core/Repository/LanguageServiceTest.php index 4d26df94a6..de8d4f0971 100644 --- a/tests/integration/Core/Repository/LanguageServiceTest.php +++ b/tests/integration/Core/Repository/LanguageServiceTest.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\LanguageService; use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Values\Content\Language; +use Ibexa\Contracts\Core\Test\IbexaKernelTestCase; /** * Test case for operations in the LanguageService using in memory storage. @@ -22,20 +23,32 @@ * @group integration * @group language */ -final class LanguageServiceTest extends BaseTestCase +final class LanguageServiceTest extends IbexaKernelTestCase { + use AssertPropertiesTrait; + private Repository $repository; private LanguageService $languageService; protected function setUp(): void { - parent::setUp(); + self::bootKernel(); + + self::loadSchema(); + self::loadFixtures(); - $this->repository = $this->getRepository(); + self::setAdministratorUser(); + + $this->repository = self::getServiceByClassName(Repository::class); $this->languageService = $this->repository->getContentLanguageService(); } + private function generateId(string $type, string $rawId): string + { + return self::getServiceByClassName(IdManager::class)->generateId($type, $rawId); + } + public function testNewLanguageCreateStruct(): void { $languageCreate = $this->languageService->newLanguageCreateStruct(); @@ -113,6 +126,33 @@ public function testCreateLanguageThrowsInvalidArgumentException(): void $this->languageService->createLanguage($languageCreate); } + public function testCreateLanguageWithEmptyLanguageCode(): void + { + $languageCreate = $this->languageService->newLanguageCreateStruct(); + $languageCreate->enabled = true; + $languageCreate->name = 'English'; + $languageCreate->languageCode = ''; + + $this->expectException(InvalidArgumentException::class); + $this->languageService->createLanguage($languageCreate); + } + + /** + * @testWith ["."] + * ["ąę"] + * ["%^"] + */ + public function testCreateLanguageWithInvalidLanguageCode(): void + { + $languageCreate = $this->languageService->newLanguageCreateStruct(); + $languageCreate->enabled = true; + $languageCreate->name = 'English'; + $languageCreate->languageCode = 'eng 123'; + + $this->expectException(InvalidArgumentException::class); + $this->languageService->createLanguage($languageCreate); + } + /** * @depends testCreateLanguage */ diff --git a/tests/integration/Core/Resources/services/services.php b/tests/integration/Core/Resources/services/services.php new file mode 100644 index 0000000000..dc3888379b --- /dev/null +++ b/tests/integration/Core/Resources/services/services.php @@ -0,0 +1,24 @@ +services(); + $services->defaults() + ->autowire() + ->autoconfigure() + ->private(); + + $services->set('test.' . IdManager::class, Php::class) + ->public() + ; +}; From bf432f91bda1a1999127e34416ed15e74b4cdd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 23 Mar 2026 16:55:44 +0100 Subject: [PATCH 3/7] Refactored `LanguageServiceTest` and added stricter validation for `LanguageCreateStruct` --- phpstan-baseline.neon | 226 +----------------- tests/integration/Core/CoreTestKernel.php | 5 + .../Core/Repository/AssertPropertiesTrait.php | 51 ++-- .../Core/Repository/BaseTestCase.php | 3 - .../Core/Repository/LanguageServiceTest.php | 2 +- 5 files changed, 37 insertions(+), 250 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d596595cf0..3e7b570f19 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -23292,60 +23292,6 @@ parameters: count: 1 path: tests/integration/Core/Repository/BaseTestCase.php - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesCorrect\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesCorrectUnsorted\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesEqual\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesEqual\(\) has parameter \$actualValue with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesEqual\(\) has parameter \$expectedValue with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesEqual\(\) has parameter \$propertyName with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertPropertiesEqual\(\) has parameter \$sortArray with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertStructPropertiesCorrect\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:assertStructPropertiesCorrect\(\) has parameter \$additionalProperties with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:createFolder\(\) has parameter \$names with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -23388,48 +23334,18 @@ parameters: count: 1 path: tests/integration/Core/Repository/BaseTestCase.php - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:sortItems\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:sortItems\(\) has parameter \$items with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:updateFolder\(\) has parameter \$names with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue count: 1 path: tests/integration/Core/Repository/BaseTestCase.php - - - message: '#^PHPDoc tag @param references unknown parameter\: \$propertyNames$#' - identifier: parameter.notFound - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - message: '#^PHPDoc tag @var for variable \$searchHandler contains unknown class Ibexa\\Solr\\Handler\.$#' identifier: class.notFound count: 1 path: tests/integration/Core/Repository/BaseTestCase.php - - - message: '#^Parameter \#1 \$string1 of function strcmp expects string, bool\|float\|int\|string given\.$#' - identifier: argument.type - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - - - message: '#^Parameter \#2 \$string2 of function strcmp expects string, bool\|float\|int\|string given\.$#' - identifier: argument.type - count: 1 - path: tests/integration/Core/Repository/BaseTestCase.php - - message: '#^Property Ibexa\\Tests\\Integration\\Core\\Repository\\BaseTestCase\:\:\$repository \(Ibexa\\Contracts\\Core\\Repository\\Repository\) does not accept null\.$#' identifier: assign.propertyType @@ -31663,146 +31579,8 @@ parameters: path: tests/integration/Core/Repository/LanguageServiceMaximumSupportedLanguagesTest.php - - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Language'' and Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Language will always evaluate to true\.$#' - identifier: staticMethod.alreadyNarrowedType - count: 3 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\LanguageCreateStruct'' and Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\LanguageCreateStruct will always evaluate to true\.$#' - identifier: staticMethod.alreadyNarrowedType - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:loadLanguagesReturnsAnEmptyArrayByDefault\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testCreateLanguageInTransactionWithCommit\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testCreateLanguageInTransactionWithRollback\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testCreateLanguageSetsExpectedProperties\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testCreateLanguageSetsIdPropertyOnReturnedLanguage\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testCreateLanguageThrowsInvalidArgumentException\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testDeleteLanguage\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testDeleteLanguageThrowsInvalidArgumentException\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testDisableLanguage\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testEnableLanguage\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testGetDefaultLanguageCode\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguage\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguageById\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguageByIdThrowsNotFoundException\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguageThrowsInvalidArgumentException\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguageThrowsNotFoundException\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testLoadLanguages\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testNewLanguageCreateStruct\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testUpdateLanguageName\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testUpdateLanguageNameInTransactionWithCommit\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testUpdateLanguageNameInTransactionWithRollback\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: tests/integration/Core/Repository/LanguageServiceTest.php - - - - message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\LanguageServiceTest\:\:testUpdateLanguageNameThrowsInvalidArgumentException\(\) has no return type specified\.$#' - identifier: missingType.return + message: '#^Argument of an invalid type Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable count: 1 path: tests/integration/Core/Repository/LanguageServiceTest.php diff --git a/tests/integration/Core/CoreTestKernel.php b/tests/integration/Core/CoreTestKernel.php index 361f58437d..6d4d361827 100644 --- a/tests/integration/Core/CoreTestKernel.php +++ b/tests/integration/Core/CoreTestKernel.php @@ -1,5 +1,10 @@ $propertyValue) { if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + self::assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); } elseif (is_array($propertyValue)) { foreach ($propertyValue as $key => $value) { if ($value instanceof ValueObject) { - $this->assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]); + self::assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]); } else { - $this->assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]); + self::assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]); } } } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); + self::assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); } } } @@ -52,9 +55,9 @@ protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueO { foreach ($expectedValues as $propertyName => $propertyValue) { if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + self::assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true); + self::assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true); } } } @@ -66,21 +69,21 @@ protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueO * * @param array $additionalProperties */ - protected function assertStructPropertiesCorrect( + protected static function assertStructPropertiesCorrect( ValueObject $expectedValues, ValueObject $actualObject, array $additionalProperties = [], ): void { foreach ($expectedValues as $propertyName => $propertyValue) { if ($propertyValue instanceof ValueObject) { - $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); + self::assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName); } else { - $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); + self::assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName); } } foreach ($additionalProperties as $propertyName) { - $this->assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName); + self::assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName); } } @@ -89,34 +92,38 @@ protected function assertStructPropertiesCorrect( * * @see \Ibexa\Tests\Integration\Core\Repository\BaseTestCase::assertPropertiesCorrectUnsorted */ - private function sortItems(array &$items): void + private static function sortItems(array &$items): void { $sorter = static function ($a, $b): int { if (!is_scalar($a) || !is_scalar($b)) { self::fail('Wrong usage: method ' . __METHOD__ . ' accepts only an array of scalar values'); } - return strcmp($a, $b); + return strcmp((string)$a, (string)$b); }; usort($items, $sorter); } - private function assertPropertiesEqual($propertyName, $expectedValue, $actualValue, $sortArray = false): void - { - if ($expectedValue instanceof \ArrayObject) { + private static function assertPropertiesEqual( + string $propertyName, + mixed $expectedValue, + mixed $actualValue, + bool $sortArray = false + ): void { + if ($expectedValue instanceof ArrayObject) { $expectedValue = $expectedValue->getArrayCopy(); - } elseif ($expectedValue instanceof \DateTimeInterface) { - $expectedValue = $expectedValue->format(\DateTime::RFC850); + } elseif ($expectedValue instanceof DateTimeInterface) { + $expectedValue = $expectedValue->format(DateTime::RFC850); } - if ($actualValue instanceof \ArrayObject) { + if ($actualValue instanceof ArrayObject) { $actualValue = $actualValue->getArrayCopy(); - } elseif ($actualValue instanceof \DateTimeInterface) { - $actualValue = $actualValue->format(\DateTime::RFC850); + } elseif ($actualValue instanceof DateTimeInterface) { + $actualValue = $actualValue->format(DateTime::RFC850); } if ($sortArray && is_array($actualValue) && is_array($expectedValue)) { - $this->sortItems($actualValue); - $this->sortItems($expectedValue); + self::sortItems($actualValue); + self::sortItems($expectedValue); } self::assertEquals( diff --git a/tests/integration/Core/Repository/BaseTestCase.php b/tests/integration/Core/Repository/BaseTestCase.php index 1d9e3319e5..6a96d112d0 100644 --- a/tests/integration/Core/Repository/BaseTestCase.php +++ b/tests/integration/Core/Repository/BaseTestCase.php @@ -7,8 +7,6 @@ namespace Ibexa\Tests\Integration\Core\Repository; -use ArrayObject; -use DateTime; use Doctrine\DBAL\Connection; use ErrorException; use Exception; @@ -25,7 +23,6 @@ use Ibexa\Contracts\Core\Repository\Values\User\User; use Ibexa\Contracts\Core\Repository\Values\User\UserGroup; use Ibexa\Contracts\Core\Repository\Values\User\UserReference; -use Ibexa\Contracts\Core\Repository\Values\ValueObject; use Ibexa\Contracts\Core\Test\Repository\SetupFactory; use Ibexa\Contracts\Core\Test\Repository\SetupFactory\Legacy as LegacySetupFactory; use Ibexa\Tests\Core\Repository\PHPUnitConstraint; diff --git a/tests/integration/Core/Repository/LanguageServiceTest.php b/tests/integration/Core/Repository/LanguageServiceTest.php index de8d4f0971..97a19bf313 100644 --- a/tests/integration/Core/Repository/LanguageServiceTest.php +++ b/tests/integration/Core/Repository/LanguageServiceTest.php @@ -44,7 +44,7 @@ protected function setUp(): void $this->languageService = $this->repository->getContentLanguageService(); } - private function generateId(string $type, string $rawId): string + private function generateId(string $type, int $rawId): int { return self::getServiceByClassName(IdManager::class)->generateId($type, $rawId); } From 3306ab071db7195372c6f56ea4b00aa61c886a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 23 Mar 2026 16:57:52 +0100 Subject: [PATCH 4/7] Refactored `LanguageServiceTest` and added stricter validation for `LanguageCreateStruct` --- src/lib/Repository/LanguageService.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/Repository/LanguageService.php b/src/lib/Repository/LanguageService.php index 07ec48062e..07de7c2098 100644 --- a/src/lib/Repository/LanguageService.php +++ b/src/lib/Repository/LanguageService.php @@ -62,12 +62,9 @@ public function __construct( /** * Creates the a new Language in the content repository. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\LanguageCreateStruct $languageCreateStruct - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Language - * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException If user does not have access to content translations * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if the languageCode already exists + * @throws \Ibexa\Contracts\Core\Validation\ValidationFailedException */ public function createLanguage(LanguageCreateStruct $languageCreateStruct): Language { From 4f830d7f28ddc14f2125f7f38fee1eb7eeecebda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 23 Mar 2026 17:39:46 +0100 Subject: [PATCH 5/7] Refactored `LanguageServiceTest` to improve parameterization for invalid language code tests --- tests/integration/Core/Repository/LanguageServiceTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/Core/Repository/LanguageServiceTest.php b/tests/integration/Core/Repository/LanguageServiceTest.php index 97a19bf313..c93ce9c6b3 100644 --- a/tests/integration/Core/Repository/LanguageServiceTest.php +++ b/tests/integration/Core/Repository/LanguageServiceTest.php @@ -141,13 +141,14 @@ public function testCreateLanguageWithEmptyLanguageCode(): void * @testWith ["."] * ["ąę"] * ["%^"] + * ["eng 123"] */ - public function testCreateLanguageWithInvalidLanguageCode(): void + public function testCreateLanguageWithInvalidLanguageCode(string $languageCode): void { $languageCreate = $this->languageService->newLanguageCreateStruct(); $languageCreate->enabled = true; $languageCreate->name = 'English'; - $languageCreate->languageCode = 'eng 123'; + $languageCreate->languageCode = $languageCode; $this->expectException(InvalidArgumentException::class); $this->languageService->createLanguage($languageCreate); From acf21b817926d3d816d5cac8c0c242ec17be724b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 24 Mar 2026 10:17:14 +0100 Subject: [PATCH 6/7] Updated `KERNEL_CLASS` in PHPUnit configuration to use `CoreTestKernel` instead of `IbexaSolrTestKernel` --- phpunit-integration-legacy-solr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit-integration-legacy-solr.xml b/phpunit-integration-legacy-solr.xml index d7289a08bc..e3ddf67d94 100644 --- a/phpunit-integration-legacy-solr.xml +++ b/phpunit-integration-legacy-solr.xml @@ -13,7 +13,7 @@ - + From 310c0a85bf7750e786a8e1488e72abba06e7c1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 24 Mar 2026 11:02:40 +0100 Subject: [PATCH 7/7] Updated `KERNEL_CLASS` in PHPUnit configuration to use `IbexaSolrTestKernel` and added `CoreSolrTestKernel` implementation --- phpunit-integration-legacy-solr.xml | 2 +- tests/integration/Core/CoreSolrTestKernel.php | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/integration/Core/CoreSolrTestKernel.php diff --git a/phpunit-integration-legacy-solr.xml b/phpunit-integration-legacy-solr.xml index e3ddf67d94..d7289a08bc 100644 --- a/phpunit-integration-legacy-solr.xml +++ b/phpunit-integration-legacy-solr.xml @@ -13,7 +13,7 @@ - + diff --git a/tests/integration/Core/CoreSolrTestKernel.php b/tests/integration/Core/CoreSolrTestKernel.php new file mode 100644 index 0000000000..f93402d020 --- /dev/null +++ b/tests/integration/Core/CoreSolrTestKernel.php @@ -0,0 +1,21 @@ +load(__DIR__ . '/Resources/services/services.php'); + } +}