From 2d0eddaf51044238d0be0f362802ea2bf79c6c19 Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 18 Dec 2025 16:43:48 +0100 Subject: [PATCH] test on app controller --- .gitignore | 1 + composer.json | 1 + composer.lock | 71 ++++- config/bundles.php | 1 + .../packages/dama_doctrine_test_bundle.yaml | 5 + src/Controller/ApplicationController.php | 74 +++-- src/Entity/Apps.php | 1 + symfony.lock | 12 + templates/application/appSmall.html.twig | 2 +- templates/application/index.html.twig | 14 +- .../Controller/ApplicationControllerTest.php | 301 ++++++++++++++++++ tests/Functional/AbstractFunctionalTest.php | 72 +++++ 12 files changed, 521 insertions(+), 34 deletions(-) create mode 100644 config/packages/dama_doctrine_test_bundle.yaml create mode 100644 tests/Controller/ApplicationControllerTest.php create mode 100644 tests/Functional/AbstractFunctionalTest.php diff --git a/.gitignore b/.gitignore index 2fa901b..8d4195a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ ###> symfony/framework-bundle ### /.env.local +/.env.test /.env.local.php /.env.*.local /config/secrets/prod/prod.decrypt.private.php diff --git a/composer.json b/composer.json index 20e0ac9..3ab5270 100644 --- a/composer.json +++ b/composer.json @@ -107,6 +107,7 @@ } }, "require-dev": { + "dama/doctrine-test-bundle": "^8.3", "phpunit/phpunit": "^11.0", "symfony/browser-kit": "7.2.*", "symfony/css-selector": "7.2.*", diff --git a/composer.lock b/composer.lock index 8611449..4c2dcff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "00e62b0a959e7b09d4b1fdb7e0501549", + "content-hash": "9c6693b9e0508ab0c1ff3ee95823be7d", "packages": [ { "name": "aws/aws-crt-php", @@ -9995,6 +9995,75 @@ } ], "packages-dev": [ + { + "name": "dama/doctrine-test-bundle", + "version": "v8.3.1", + "source": { + "type": "git", + "url": "https://github.com/dmaicher/doctrine-test-bundle.git", + "reference": "9bc47e02a0d67cbfef6773837249f71e65c95bf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/9bc47e02a0d67cbfef6773837249f71e65c95bf6", + "reference": "9bc47e02a0d67cbfef6773837249f71e65c95bf6", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.3 || ^4.0", + "doctrine/doctrine-bundle": "^2.11.0", + "php": ">= 8.1", + "psr/cache": "^2.0 || ^3.0", + "symfony/cache": "^6.4 || ^7.2 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.2 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<10.0" + }, + "require-dev": { + "behat/behat": "^3.0", + "friendsofphp/php-cs-fixer": "^3.27", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/process": "^6.4 || ^7.2 || ^8.0", + "symfony/yaml": "^6.4 || ^7.2 || ^8.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "DAMA\\DoctrineTestBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Maicher", + "email": "mail@dmaicher.de" + } + ], + "description": "Symfony bundle to isolate doctrine database tests and improve test performance", + "keywords": [ + "doctrine", + "isolation", + "performance", + "symfony", + "testing", + "tests" + ], + "support": { + "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.3.1" + }, + "time": "2025-08-05T17:55:02+00:00" + }, { "name": "masterminds/html5", "version": "2.10.0", diff --git a/config/bundles.php b/config/bundles.php index d17bbf8..c967433 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -19,4 +19,5 @@ return [ Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true], Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true], Aws\Symfony\AwsBundle::class => ['all' => true], + DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], ]; diff --git a/config/packages/dama_doctrine_test_bundle.yaml b/config/packages/dama_doctrine_test_bundle.yaml new file mode 100644 index 0000000..3482cba --- /dev/null +++ b/config/packages/dama_doctrine_test_bundle.yaml @@ -0,0 +1,5 @@ +when@test: + dama_doctrine_test: + enable_static_connection: true + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/src/Controller/ApplicationController.php b/src/Controller/ApplicationController.php index a666a8e..0b4e776 100644 --- a/src/Controller/ApplicationController.php +++ b/src/Controller/ApplicationController.php @@ -12,6 +12,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; #[Route(path: '/application', name: 'application_')] @@ -41,8 +42,8 @@ class ApplicationController extends AbstractController $this->loggerService->logEntityNotFound('Application', [ 'applicationId' => $id, 'message' => "Application not found for editing." - ], $actingUser); - $this->addFlash('error', "L'application n'existe pas ou n'est pas reconnu."); + ], $actingUser->getId()); + $this->addFlash('danger', "L'application n'existe pas ou n'est pas reconnu."); return $this->redirectToRoute('application_index'); } $applicationData = [ @@ -61,13 +62,12 @@ class ApplicationController extends AbstractController $application->setDescription($data['description']); $application->setDescriptionSmall($data['descriptionSmall']); $this->entityManager->persist($application); - $this->actionService->createAction("Modification de l'application ", $actingUser->getId(), null, $application->getId()); + $this->actionService->createAction("Modification de l'application ", $actingUser, null, $application->getId()); $this->loggerService->logApplicationInformation('Application Edited', [ 'applicationId' => $application->getId(), 'applicationName' => $application->getName(), 'message' => "Application edited successfully." ], $actingUser->getId()); - $this->addFlash('success', "L'application a été mise à jour avec succès."); }catch (\Exception $e){ $this->loggerService->logError('Application Edit Failed', [ 'applicationId' => $application->getId(), @@ -75,7 +75,6 @@ class ApplicationController extends AbstractController 'error' => $e->getMessage(), 'message' => "Failed to edit application." ], $actingUser); - $this->addFlash('error', "Une erreur est survenue lors de la mise à jour de l'application."); } return $this->redirectToRoute('application_index'); @@ -91,38 +90,51 @@ class ApplicationController extends AbstractController { $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN'); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); - $application = $this->entityManager->getRepository(Apps::class)->find($id); - if (!$application) { - $this->loggerService->logEntityNotFound('Application', [ + try{ + $application = $this->entityManager->getRepository(Apps::class)->find($id); + if (!$application) { + $this->loggerService->logEntityNotFound('Application', [ + 'applicationId' => $id, + 'message' => "Application not found for authorization." + ], $actingUser->getId()); + throw $this->createNotFoundException("L'application n'existe pas."); + } + $orgId = $request->get('organizationId'); + + $organization = $this->entityManager->getRepository(Organizations::Class)->find($orgId); + if (!$organization) { + $this->loggerService->logEntityNotFound('Organization', [ + 'Organization_id' => $orgId, + 'message' => "Organization not found for authorization." + ], $actingUser->getId()); + throw $this->createNotFoundException("L'Organization n'existe pas."); + } + $application->addOrganization($organization); + $this->loggerService->logApplicationInformation('Application Authorized', [ + 'applicationId' => $application->getId(), + 'applicationName' => $application->getName(), + 'organizationId' => $organization->getId(), + 'message' => "Application authorized for organization." + ], $actingUser->getId()); + $this->entityManager->persist($application); + $this->entityManager->flush(); + $this->actionService->createAction("Authorization d'accès", $actingUser, $organization, $application->getName()); + return new Response('', Response::HTTP_OK); + }catch (\Exception $e){ + $this->loggerService->logError('Application Authorization Failed', [ 'applicationId' => $id, - 'message' => "Application not found for authorization." - ], $actingUser->getId()); - throw $this->createNotFoundException("L'application n'existe pas."); + 'error' => $e->getMessage(), + 'message' => "Failed to authorize application.", + 'acting_user_id' => $actingUser->getId() + ]); + return new Response('Erreur lors de l\'autorisation de l\'application.', Response::HTTP_INTERNAL_SERVER_ERROR); } - $orgId = $request->get('organizationId'); - $organization = $this->entityManager->getRepository(Organizations::Class)->find($orgId); - if (!$organization) { - $this->loggerService->logEntityNotFound('Organization', [ - 'Organization_id' => $orgId, - 'message' => "Organization not found for authorization." - ], $actingUser->getId()); - throw $this->createNotFoundException("L'Organization n'existe pas."); - } - $application->addOrganization($organization); - $this->loggerService->logApplicationInformation('Application Authorized', [ - 'applicationId' => $application->getId(), - 'applicationName' => $application->getName(), - 'organizationId' => $organization->getId(), - 'message' => "Application authorized for organization." - ], $actingUser->getId()); - $this->actionService->createAction("Authorization d'accès", $actingUser, $organization, $application->getName()); - return new Response('', Response::HTTP_OK); } - #[Route(path: '/remove/{id}', name: 'remove', methods: ['POST'])] - public function remove(int $id, Request $request) + #[Route(path: '/revoke/{id}', name: 'revoke', methods: ['POST'])] + public function revoke(int $id, Request $request) { $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN'); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); diff --git a/src/Entity/Apps.php b/src/Entity/Apps.php index 1492d3b..99c6f7e 100644 --- a/src/Entity/Apps.php +++ b/src/Entity/Apps.php @@ -59,6 +59,7 @@ class Apps { $this->userOrganizatonApps = new ArrayCollection(); $this->organization = new ArrayCollection(); + $this->setIsActive(true); } public function getId(): ?int diff --git a/symfony.lock b/symfony.lock index ef4308d..0644b1a 100644 --- a/symfony.lock +++ b/symfony.lock @@ -11,6 +11,18 @@ "config/packages/aws.yaml" ] }, + "dama/doctrine-test-bundle": { + "version": "8.3", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "8.3", + "ref": "dfc51177476fb39d014ed89944cde53dc3326d23" + }, + "files": [ + "config/packages/dama_doctrine_test_bundle.yaml" + ] + }, "doctrine/deprecations": { "version": "1.1", "recipe": { diff --git a/templates/application/appSmall.html.twig b/templates/application/appSmall.html.twig index 14faa93..77fd5e4 100644 --- a/templates/application/appSmall.html.twig +++ b/templates/application/appSmall.html.twig @@ -14,7 +14,7 @@ {% if application.hasAccess %} {% if is_granted("ROLE_SUPER_ADMIN") %}
+ {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %}

Bienvenue sur la suite Easy

-

Ici, vous pouvez trouver toutes nos applications à un seul endroit !

+

Ici, vous pouvez trouver toutes nos applications à un seul endroit !

+ {% if applications is empty %} + + {% endif %} {% for application in applications %}
{% include 'application/InformationCard.html.twig' with { diff --git a/tests/Controller/ApplicationControllerTest.php b/tests/Controller/ApplicationControllerTest.php new file mode 100644 index 0000000..4102e60 --- /dev/null +++ b/tests/Controller/ApplicationControllerTest.php @@ -0,0 +1,301 @@ +client->request('GET', '/application/'); + self::assertResponseRedirects('/login'); // Assuming your login route is /login + } + + #[Test] + public function index_lists_applications_for_authenticated_user(): void + { + // 1. Arrange: Create User and Data + $user = $this->createUser('user@test.com'); + $this->createApp('App One'); + $this->createApp('App Two'); + + // 2. Act: Login and Request + $this->client->loginUser($user); + $this->client->request('GET', '/application/'); + + // 3. Assert + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('body', 'App One'); + self::assertSelectorTextContains('body', 'App Two'); + } + + + #[Test] + public function index_no_application_found(): void + { + $user = $this->createUser('user@test.com'); + $this->client->loginUser($user); + $this->client->request('GET', '/application/'); + + self::assertResponseIsSuccessful(); + self::assertSelectorTextContains('body', 'Aucune application disponible'); + } + + //endregion + + //region Edit Tests + + #[Test] + public function edit_page_denies_access_to_regular_users(): void + { + $user = $this->createUser('regular@test.com'); + $app = $this->createApp('Target App'); + + $this->client->loginUser($user); + $this->client->request('GET', '/application/edit/' . $app->getId()); + + self::assertResponseStatusCodeSame(403); + } + #[Test] + public function edit_page_denies_access_to_admin_users(): void + { + $user = $this->createUser('admin@test.com', ['ROLE_ADMIN']); + $app = $this->createApp('Target App'); + + $this->client->loginUser($user); + $this->client->request('GET', '/application/edit/' . $app->getId()); + + self::assertResponseStatusCodeSame(403); + } + + #[Test] + public function edit_page_loads_for_super_admin(): void + { + $admin = $this->createUser('admin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('Editable App'); + + $this->client->loginUser($admin); + $crawler = $this->client->request('GET', '/application/edit/' . $app->getId()); + + self::assertResponseIsSuccessful(); + $this->assertCount(1, $crawler->filter('input[name="name"]')); + } + + #[Test] + public function edit_submits_changes_successfully(): void + { + $admin = $this->createUser('admin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('Old Name'); + + $this->client->loginUser($admin); + + // Simulate POST request directly (mimicking form submission) + $this->client->request('POST', '/application/edit/' . $app->getId(), [ + 'name' => 'New Name', + 'description' => 'Updated Description', + 'descriptionSmall' => 'Updated Small', + ]); + + // Assert Redirection + self::assertResponseRedirects('/application/'); + $this->client->followRedirect(); + + // Assert Database Update + $this->entityManager->clear(); // Clear identity map to force fresh fetch + $updatedApp = $this->entityManager->getRepository(Apps::class)->find($app->getId()); + $this->assertEquals('New Name', $updatedApp->getName()); + $this->assertEquals('Updated Description', $updatedApp->getDescription()); + } + + #[Test] + public function edit_handles_non_existent_id_get(): void + { + $admin = $this->createUser('admin@test.com', ['ROLE_SUPER_ADMIN']); + $this->client->loginUser($admin); + + $this->client->request('GET', '/application/edit/999999'); + + self::assertResponseRedirects('/application/'); + $this->client->followRedirect(); + self::assertSelectorExists('.alert-danger'); + + self::assertSelectorTextContains('.alert-danger', "n'existe pas"); + } + + #[Test] + public function edit_handles_non_existent_id_post(): void + { + // Arrange + $admin = $this->createUser('superAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('App With Issue'); + $this->client->loginUser($admin); + + $this->client->request('POST', '/application/edit/' . 99999, [ + 'name' => 'New Name', + 'description' => 'Updated Description', + 'descriptionSmall' => 'Updated Small', + ]); + + self::assertResponseRedirects('/application/'); + $this->client->followRedirect(); + self::assertSelectorExists('.alert-danger'); + self::assertSelectorTextContains('.alert-danger', "n'existe pas"); + } + + //endregion + + //region Authorize Tests + #[Test] + public function authorize_adds_organization_successfully(): void + { + $admin = $this->createUser('admin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('Auth App'); + $org = $this->createOrganization('Test Org'); + + $this->client->loginUser($admin); + + $this->client->request('POST', '/application/authorize/' . $app->getId(), [ + 'organizationId' => $org->getId() + ]); + + self::assertResponseStatusCodeSame(200); + + // Clear Doctrine memory to force fetching fresh data from DB + $this->entityManager->clear(); + + $updatedApp = $this->entityManager->getRepository(Apps::class)->find($app->getId()); + + $exists = $updatedApp->getOrganization()->exists(function($key, $element) use ($org) { + return $element->getId() === $org->getId(); + }); + + $this->assertTrue($exists, 'The application is not linked to the organization.'); + } + + #[Test] + public function authorize_fails_on_invalid_organization(): void + { + $admin = $this->createUser('sAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('App For Org Test'); + + $this->client->loginUser($admin); + $this->client->request('POST', '/application/authorize/' . $app->getId(), [ + 'organizationId' => 99999 + ]); + + self::assertResponseStatusCodeSame(404); + + } + + #[Test] + public function authorize_fails_on_invalid_application(): void + { + $admin = $this->createUser('sAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $this->client->loginUser($admin); + $this->client->request('POST', '/application/authorize/99999', [ + 'organizationId' => 1 + ]); + self::assertResponseStatusCodeSame(404); + } + //endregion + + //region revoke Tests + + #[Test] + public function revoke_denies_access_to_admins(): void + { + $user = $this->createUser('Admin@test.com', ['ROLE_ADMIN']); + $app = $this->createApp('App To Revoke'); + $org = $this->createOrganization('Org To Revoke'); + $this->client->loginUser($user); + $this->client->request('POST', '/application/revoke/'. $app->getId(), [ + 'organizationId' => $org->getId() + ]); + self::assertResponseStatusCodeSame(403); + + } + + #[Test] + public function revoke_denies_access_to_user(): void + { + $user = $this->createUser('user@test.com'); + $app = $this->createApp('App To Revoke'); + $org = $this->createOrganization('Org To Revoke'); + $this->client->loginUser($user); + $this->client->request('POST', '/application/revoke/'. $app->getId(), [ + 'organizationId' => $org->getId() + ]); + self::assertResponseStatusCodeSame(403); + + } + + #[Test] + public function revoke_removes_organization_successfully(): void + { + $admin = $this->createUser('sAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('App To Revoke Org'); + $org = $this->createOrganization('Org To Be Revoked'); + // First, authorize the organization + $app->addOrganization($org); + $this->entityManager->persist($app); + $this->entityManager->flush(); + $this->client->loginUser($admin); + $this->client->request('POST', '/application/revoke/'. $app->getId(), [ + 'organizationId' => $org->getId() + ]); + self::assertResponseStatusCodeSame(200); + + // Clear Doctrine memory to force fetching fresh data from DB + $this->entityManager->clear(); + $updatedApp = $this->entityManager->getRepository(Apps::class)->find($app->getId()); + $exists = $updatedApp->getOrganization()->exists(function($key, $element) use ($org) { + return $element === $org; + }); + self::assertFalse($exists, 'The organization was removed from the application.'); + } + + #[Test] + public function revoke_fails_on_invalid_organization(): void + { + $admin = $this->createUser('sAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $app = $this->createApp('App To Revoke Org'); + $org = $this->createOrganization('Org To Be Revoked'); + // First, authorize the organization + $app->addOrganization($org); + $this->entityManager->persist($app); + $this->entityManager->flush(); + $this->client->loginUser($admin); + $this->client->request('POST', '/application/revoke/' . $app-> +getId(), [ + 'organizationId' => 99999 + ]); + self::assertResponseStatusCodeSame(404); + } + + #[Test] + public function revoke_fails_on_invalid_application(): void + { + $admin = $this->createUser('sAdmin@test.com', ['ROLE_SUPER_ADMIN']); + $org = $this->createOrganization('Org To Be Revoked'); + // First, authorize the organization + $this->client->loginUser($admin); + $this->client->request('POST', '/application/revoke/' . 9999, [ + 'organizationId' => 99999 + ]); + self::assertResponseStatusCodeSame(404, "L'application n'existe pas."); + } + //endregion + + + + +} \ No newline at end of file diff --git a/tests/Functional/AbstractFunctionalTest.php b/tests/Functional/AbstractFunctionalTest.php new file mode 100644 index 0000000..c5209f4 --- /dev/null +++ b/tests/Functional/AbstractFunctionalTest.php @@ -0,0 +1,72 @@ +client = static::createClient(); + + // Access the container to get the EntityManager + $this->entityManager = static::getContainer()->get(EntityManagerInterface::class); + } + + protected function createUser(string $email, array $roles = ['ROLE_USER']): User + { + $user = new User(); + $user->setEmail($email); + $user->setRoles($roles); + $user->setName('Test'); + $user->setSurname('User'); + $user->setPassword('$2y$13$...'); // Dummy hash, logic typically bypassed by loginUser + + $this->entityManager->persist($user); + $this->entityManager->flush(); + + return $user; + } + + protected function createApp(string $name): Apps + { + // Based on your Entity, these fields are NOT nullable, so we must fill them + $app = new Apps(); + $app->setName($name); + $app->setTitle($name . ' Title'); + $app->setSubDomain(strtolower($name)); // Assuming valid subdomain logic + $app->setLogoUrl('https://example.com/logo.png'); + // $app->setDescription() is nullable, so we can skip or set it + + $this->entityManager->persist($app); + $this->entityManager->flush(); + + return $app; + } + + protected function createOrganization(string $name): Organizations + { + // I am assuming the Organizations entity structure here based on context + $org = new Organizations(); + $org->setName($name); + $org->setEmail('contact@' . strtolower($name) . '.com'); + $org->setNumber(100 + rand(1, 900)); // Example number + $org->setAddress('123 ' . $name . ' St'); // Example address + $org->setLogoUrl('https://example.com/org_logo.png'); + // Add other required fields if Organizations has non-nullable columns + + $this->entityManager->persist($org); + $this->entityManager->flush(); + + return $org; + } +} \ No newline at end of file