From df8a701c2f090ec8217df612555f1b63bc9a012d Mon Sep 17 00:00:00 2001 From: Kevin Raynel Date: Wed, 20 Sep 2023 17:39:31 +0200 Subject: [PATCH 1/5] Tests: Add first simple test --- .gitignore | 1 + composer.json | 13 +++- tests/BundleInitializationTest.php | 97 +++++++++++++++++++++++++ tests/Entity/Book.php | 21 ++++++ tests/config/packages/api_platform.yaml | 3 + tests/config/packages/doctrine.yaml | 3 + tests/config/routes.yaml | 4 + 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tests/BundleInitializationTest.php create mode 100644 tests/Entity/Book.php create mode 100644 tests/config/packages/api_platform.yaml create mode 100644 tests/config/packages/doctrine.yaml create mode 100644 tests/config/routes.yaml diff --git a/.gitignore b/.gitignore index 57872d0..3a9875b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /vendor/ +composer.lock diff --git a/composer.json b/composer.json index 3ecd49f..0f6d47b 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,17 @@ "require-dev": { "phpstan/phpstan": "^1.8", "friendsofphp/php-cs-fixer": "^3.9", - "api-platform/symfony": "^3.0" + "api-platform/symfony": "^3.0", + "phpunit/phpunit": "^10", + "nyholm/symfony-bundle-test": "^2.0", + "symfony/expression-language": "^6.3", + "doctrine/doctrine-bundle": "^2.10", + "doctrine/orm": "^2.16", + "symfony/security-bundle": "^6.3", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpdoc-parser": "^1.24", + "symfony/property-access": "^6.3", + "symfony/property-info": "^6.3", + "symfony/serializer": "^6.3" } } diff --git a/tests/BundleInitializationTest.php b/tests/BundleInitializationTest.php new file mode 100644 index 0000000..a6c3aef --- /dev/null +++ b/tests/BundleInitializationTest.php @@ -0,0 +1,97 @@ +addTestBundle(DoctrineBundle::class); + $kernel->addTestBundle(ApiPlatformBundle::class); + $kernel->addTestBundle(TheodoAccentBundle::class); + $kernel->addTestConfig(__DIR__.'/config/packages/doctrine.yaml'); + $kernel->addTestConfig(__DIR__.'/config/packages/api_platform.yaml'); + $kernel->addTestRoutingFile(__DIR__.'/config/routes.yaml'); + + // Unused, non public services are removed during kernel boot. + // We force them as public during test. + $kernel->addTestCompilerPass(new class () implements CompilerPassInterface { + public function process(ContainerBuilder $container): void + { + $services = $container->getDefinitions() + $container->getAliases(); + + foreach ($services as $id => $definition) { + if (stripos($id, 'theodo_accent') === 0) { + $definition->setPublic(true); + } + } + } + }); + + $kernel->handleOptions($options); + + return $kernel; + } + + public function testInitBundle(): void + { + $container = self::getContainer(); + + $this->assertTrue($container->has('theodo_accent.access_control_checker_command')); + $service = $container->get('theodo_accent.access_control_checker_command'); + $this->assertInstanceOf(Command::class, $service); + } + + public function testExposedRoutes(): void + { + $container = self::getContainer(); + + $this->assertTrue($container->has('theodo_accent.accent_report_factory')); + + /** @var AccentReportFactory $reportFactory */ + $reportFactory = $container->get('theodo_accent.accent_report_factory'); + $accentReport = $reportFactory->createAccentReport(); + + $this->assertEquals(2, $accentReport->getUnprotectedRoutesCount()); + + $resourceRelatedRoutes = array_filter( + $accentReport->getRouteAccessControlList(), + fn ($route) => RouteAccessControlData::RESOURCE_UNRELATED_ROUTE !== $route->getExpression() + ); + + $this->assertCount(3, $resourceRelatedRoutes); + $checkedRoutes = []; + foreach($resourceRelatedRoutes as $routeData) { + $checkedRoutes[$routeData->getRouteName()] = $routeData->getExpression(); + } + + $expectedRoutes = [ + '_api_/books/{id}{._format}_get' => "is_granted('ROLE_USER_GET')", + '_api_/books{._format}_post' => "is_granted('ROLE_USER_DEFAULT')", + '_api_/books/{id}{._format}_patch' => "is_granted('ROLE_USER_PATCH')", + ]; + + $this->assertEquals($expectedRoutes, $checkedRoutes); + } +} diff --git a/tests/Entity/Book.php b/tests/Entity/Book.php new file mode 100644 index 0000000..44b5606 --- /dev/null +++ b/tests/Entity/Book.php @@ -0,0 +1,21 @@ + Date: Thu, 21 Sep 2023 09:33:13 +0200 Subject: [PATCH 2/5] Add github action --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..65072b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: Symfony + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + +permissions: + contents: read + +jobs: + symfony-tests: + runs-on: ubuntu-latest + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + - uses: actions/checkout@v3 + - name: Copy .env.test.local + run: php -r "file_exists('.env.test.local') || copy('.env.test', '.env.test.local');" + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + - name: Install Dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + - name: Execute tests (Unit and Feature tests) via PHPUnit + run: vendor/bin/phpunit tests From 4655610780a9666c6ee00aa05ab09d862414259f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Touz=C3=A9?= Date: Mon, 24 Nov 2025 12:18:17 +0100 Subject: [PATCH 3/5] update CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65072b0..f3a574e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,8 @@ jobs: steps: - uses: shivammathur/setup-php@v2 with: - php-version: "8.1" - - uses: actions/checkout@v3 + php-version: "8.2" + - uses: actions/checkout@v4 - name: Copy .env.test.local run: php -r "file_exists('.env.test.local') || copy('.env.test', '.env.test.local');" - name: Cache Composer packages From 70c6da6fd6c61c071864fd722228a5b12ac2ade8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Touz=C3=A9?= Date: Mon, 24 Nov 2025 12:19:08 +0100 Subject: [PATCH 4/5] tests quality (phpstan & php cs fixer) --- tests/BundleInitializationTest.php | 87 +++++++++++++++--------------- tests/Entity/Book.php | 8 +-- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/tests/BundleInitializationTest.php b/tests/BundleInitializationTest.php index a6c3aef..4483b9a 100644 --- a/tests/BundleInitializationTest.php +++ b/tests/BundleInitializationTest.php @@ -2,58 +2,20 @@ declare(strict_types=1); -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Nyholm\BundleTest\TestKernel; use ApiPlatform\Symfony\Bundle\ApiPlatformBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Nyholm\BundleTest\TestKernel; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\KernelInterface; -use Theodo\AccentBundle\TheodoAccentBundle; use Theodo\AccentBundle\AccessControl\AccentReportFactory; use Theodo\AccentBundle\AccessControl\RouteAccessControlData; +use Theodo\AccentBundle\TheodoAccentBundle; class BundleInitializationTest extends KernelTestCase { - protected static function getKernelClass(): string - { - return TestKernel::class; - } - - protected static function createKernel(array $options = []): KernelInterface - { - /** - * @var TestKernel $kernel - */ - $kernel = parent::createKernel($options); - $kernel->addTestBundle(DoctrineBundle::class); - $kernel->addTestBundle(ApiPlatformBundle::class); - $kernel->addTestBundle(TheodoAccentBundle::class); - $kernel->addTestConfig(__DIR__.'/config/packages/doctrine.yaml'); - $kernel->addTestConfig(__DIR__.'/config/packages/api_platform.yaml'); - $kernel->addTestRoutingFile(__DIR__.'/config/routes.yaml'); - - // Unused, non public services are removed during kernel boot. - // We force them as public during test. - $kernel->addTestCompilerPass(new class () implements CompilerPassInterface { - public function process(ContainerBuilder $container): void - { - $services = $container->getDefinitions() + $container->getAliases(); - - foreach ($services as $id => $definition) { - if (stripos($id, 'theodo_accent') === 0) { - $definition->setPublic(true); - } - } - } - }); - - $kernel->handleOptions($options); - - return $kernel; - } - public function testInitBundle(): void { $container = self::getContainer(); @@ -82,7 +44,7 @@ public function testExposedRoutes(): void $this->assertCount(3, $resourceRelatedRoutes); $checkedRoutes = []; - foreach($resourceRelatedRoutes as $routeData) { + foreach ($resourceRelatedRoutes as $routeData) { $checkedRoutes[$routeData->getRouteName()] = $routeData->getExpression(); } @@ -94,4 +56,45 @@ public function testExposedRoutes(): void $this->assertEquals($expectedRoutes, $checkedRoutes); } + + protected static function getKernelClass(): string + { + return TestKernel::class; + } + + /** + * @param array{environment?: string, debug?: string} $options + */ + protected static function createKernel(array $options = []): KernelInterface + { + /** + * @var TestKernel $kernel + */ + $kernel = parent::createKernel($options); + $kernel->addTestBundle(DoctrineBundle::class); + $kernel->addTestBundle(ApiPlatformBundle::class); + $kernel->addTestBundle(TheodoAccentBundle::class); + $kernel->addTestConfig(__DIR__.'/config/packages/doctrine.yaml'); + $kernel->addTestConfig(__DIR__.'/config/packages/api_platform.yaml'); + $kernel->addTestRoutingFile(__DIR__.'/config/routes.yaml'); + + // Unused, non public services are removed during kernel boot. + // We force them as public during test. + $kernel->addTestCompilerPass(new class implements CompilerPassInterface { + public function process(ContainerBuilder $container): void + { + $services = $container->getDefinitions() + $container->getAliases(); + + foreach ($services as $id => $definition) { + if (0 === mb_stripos($id, 'theodo_accent')) { + $definition->setPublic(true); + } + } + } + }); + + $kernel->handleOptions($options); + + return $kernel; + } } diff --git a/tests/Entity/Book.php b/tests/Entity/Book.php index 44b5606..c9ea925 100644 --- a/tests/Entity/Book.php +++ b/tests/Entity/Book.php @@ -1,5 +1,7 @@ Date: Mon, 24 Nov 2025 16:49:58 +0100 Subject: [PATCH 5/5] tests: fix count of exposed route to take into accouunt the API platform internat routes --- tests/BundleInitializationTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/BundleInitializationTest.php b/tests/BundleInitializationTest.php index 4483b9a..eccb517 100644 --- a/tests/BundleInitializationTest.php +++ b/tests/BundleInitializationTest.php @@ -35,11 +35,17 @@ public function testExposedRoutes(): void $reportFactory = $container->get('theodo_accent.accent_report_factory'); $accentReport = $reportFactory->createAccentReport(); - $this->assertEquals(2, $accentReport->getUnprotectedRoutesCount()); + // 3 from API Platform _api_validation_errors routes + 2 from the Book ressource + // entrypoint and docs are present and not protected (by default). + // 3 routes for API Platform _api_validation_errors is not protected either. + $this->assertEquals(5, $accentReport->getUnprotectedRoutesCount()); $resourceRelatedRoutes = array_filter( $accentReport->getRouteAccessControlList(), fn ($route) => RouteAccessControlData::RESOURCE_UNRELATED_ROUTE !== $route->getExpression() + // Excluding the 3 _api_validation_errors_* routes that are generated by API Platform but not really exposed + // (trying to GET them generates an NotExposedHttpException => 404) + && !preg_match('/_api_validation_errors_*/', $route->getRouteName()) ); $this->assertCount(3, $resourceRelatedRoutes);