From f8472e95d876e3bd924ed3c1a4dbd8ad305d0178 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Thu, 7 Aug 2025 20:49:00 +0400 Subject: [PATCH 1/8] blacklist controller --- .../Controller/BlacklistController.php | 284 ++++++++++++++++++ .../Controller/BlacklistControllerTest.php | 60 ++++ 2 files changed, 344 insertions(+) create mode 100644 src/Subscription/Controller/BlacklistController.php create mode 100644 tests/Integration/Subscription/Controller/BlacklistControllerTest.php diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php new file mode 100644 index 0000000..90377d1 --- /dev/null +++ b/src/Subscription/Controller/BlacklistController.php @@ -0,0 +1,284 @@ +authentication = $authentication; + $this->blacklistManager = $blacklistManager; + } + + #[Route('/check/{email}', name: 'check', methods: ['GET'])] + #[OA\Get( + path: '/api/v2/blacklist/check/{email}', + description: 'Checks if an email address is blacklisted.', + summary: 'Check if email is blacklisted', + tags: ['blacklist'], + parameters: [ + new OA\Parameter( + name: 'php-auth-pw', + description: 'Session key obtained from login', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'email', + description: 'Email address to check', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'blacklisted', type: 'boolean'), + new OA\Property(property: 'reason', type: 'string', nullable: true) + ] + ), + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse') + ), + ] + )] + public function checkEmailBlacklisted(Request $request, string $email): JsonResponse + { + $admin = $this->requireAuthentication($request); + if (!$admin->getPrivileges()->has(PrivilegeFlag::Subscribers)) { + throw $this->createAccessDeniedException('You are not allowed to check blacklisted emails.'); + } + + $isBlacklisted = $this->blacklistManager->isEmailBlacklisted($email); + $reason = $isBlacklisted ? $this->blacklistManager->getBlacklistReason($email) : null; + + return $this->json([ + 'blacklisted' => $isBlacklisted, + 'reason' => $reason, + ]); + } + + #[Route('/add', name: 'add', methods: ['POST'])] + #[OA\Post( + path: '/api/v2/blacklist/add', + description: 'Adds an email address to the blacklist.', + summary: 'Add email to blacklist', + requestBody: new OA\RequestBody( + description: 'Email to blacklist', + required: true, + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'email', type: 'string'), + new OA\Property(property: 'reason', type: 'string', nullable: true) + ] + ) + ), + tags: ['blacklist'], + parameters: [ + new OA\Parameter( + name: 'php-auth-pw', + description: 'Session key obtained from login', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 201, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'success', type: 'boolean'), + new OA\Property(property: 'message', type: 'string') + ] + ), + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse') + ), + new OA\Response( + response: 422, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse') + ), + ] + )] + public function addEmailToBlacklist(Request $request): JsonResponse + { + $admin = $this->requireAuthentication($request); + if (!$admin->getPrivileges()->has(PrivilegeFlag::Subscribers)) { + throw $this->createAccessDeniedException('You are not allowed to add emails to blacklist.'); + } + + $data = json_decode($request->getContent(), true); + if (!isset($data['email']) || empty($data['email'])) { + return $this->json(['error' => 'Email is required'], Response::HTTP_UNPROCESSABLE_ENTITY); + } + + $email = $data['email']; + $reason = $data['reason'] ?? null; + + $this->blacklistManager->addEmailToBlacklist($email, $reason); + + return $this->json([ + 'success' => true, + 'message' => sprintf('Email %s has been added to the blacklist', $email), + ], Response::HTTP_CREATED); + } + + #[Route('/remove/{email}', name: 'remove', methods: ['DELETE'])] + #[OA\Delete( + path: '/api/v2/blacklist/remove/{email}', + description: 'Removes an email address from the blacklist.', + summary: 'Remove email from blacklist', + tags: ['blacklist'], + parameters: [ + new OA\Parameter( + name: 'php-auth-pw', + description: 'Session key obtained from login', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'email', + description: 'Email address to remove from blacklist', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'success', type: 'boolean'), + new OA\Property(property: 'message', type: 'string') + ] + ), + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse') + ), + ] + )] + public function removeEmailFromBlacklist(Request $request, string $email): JsonResponse + { + $admin = $this->requireAuthentication($request); + if (!$admin->getPrivileges()->has(PrivilegeFlag::Subscribers)) { + throw $this->createAccessDeniedException('You are not allowed to remove emails from blacklist.'); + } + + $this->blacklistManager->removeEmailFromBlacklist($email); + + return $this->json([ + 'success' => true, + 'message' => sprintf('Email %s has been removed from the blacklist', $email), + ]); + } + + #[Route('/info/{email}', name: 'info', methods: ['GET'])] + #[OA\Get( + path: '/api/v2/blacklist/info/{email}', + description: 'Gets detailed information about a blacklisted email.', + summary: 'Get blacklist information', + tags: ['blacklist'], + parameters: [ + new OA\Parameter( + name: 'php-auth-pw', + description: 'Session key obtained from login', + in: 'header', + required: true, + schema: new OA\Schema(type: 'string') + ), + new OA\Parameter( + name: 'email', + description: 'Email address to get information for', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string') + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'email', type: 'string'), + new OA\Property(property: 'added', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'reason', type: 'string', nullable: true) + ] + ), + ), + new OA\Response( + response: 403, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse') + ), + new OA\Response( + response: 404, + description: 'Failure', + content: new OA\JsonContent(ref: '#/components/schemas/NotFoundResponse') + ), + ] + )] + public function getBlacklistInfo(Request $request, string $email): JsonResponse + { + $admin = $this->requireAuthentication($request); + if (!$admin->getPrivileges()->has(PrivilegeFlag::Subscribers)) { + throw $this->createAccessDeniedException('You are not allowed to view blacklist information.'); + } + + $blacklistInfo = $this->blacklistManager->getBlacklistInfo($email); + if (!$blacklistInfo) { + return $this->json(['error' => sprintf('Email %s is not blacklisted', $email)], Response::HTTP_NOT_FOUND); + } + + $reason = $this->blacklistManager->getBlacklistReason($email); + + return $this->json([ + 'email' => $blacklistInfo->getEmail(), + 'added' => $blacklistInfo->getAdded() ? $blacklistInfo->getAdded()->format('c') : null, + 'reason' => $reason, + ]); + } +} diff --git a/tests/Integration/Subscription/Controller/BlacklistControllerTest.php b/tests/Integration/Subscription/Controller/BlacklistControllerTest.php new file mode 100644 index 0000000..fdba52a --- /dev/null +++ b/tests/Integration/Subscription/Controller/BlacklistControllerTest.php @@ -0,0 +1,60 @@ +get(BlacklistController::class) + ); + } + + public function testCheckEmailBlacklistedWithoutSessionKeyReturnsForbiddenStatus(): void + { + $this->jsonRequest('get', '/api/v2/blacklist/check/test@example.com'); + + $this->assertHttpForbidden(); + } + + public function testAddEmailToBlacklistWithoutSessionKeyReturnsForbiddenStatus(): void + { + $this->jsonRequest('post', '/api/v2/blacklist/add'); + + $this->assertHttpForbidden(); + } + + public function testAddEmailToBlacklistWithMissingEmailReturnsUnprocessableEntityStatus(): void + { + $jsonData = []; + + $this->authenticatedJsonRequest('post', '/api/v2/blacklist/add', [], [], [], json_encode($jsonData)); + + $this->assertHttpUnprocessableEntity(); + } + + public function testRemoveEmailFromBlacklistWithoutSessionKeyReturnsForbiddenStatus(): void + { + $this->jsonRequest('delete', '/api/v2/blacklist/remove/test@example.com'); + + $this->assertHttpForbidden(); + } + + public function testGetBlacklistInfoWithoutSessionKeyReturnsForbiddenStatus(): void + { + $this->jsonRequest('get', '/api/v2/blacklist/info/test@example.com'); + + $this->assertHttpForbidden(); + } +} From 5d17a406fa490913fc39cc764867e246f3dc6a75 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Thu, 7 Aug 2025 21:38:38 +0400 Subject: [PATCH 2/8] blacklist request --- .../Controller/BlacklistController.php | 29 +++++++------------ .../Request/AddToBlacklistRequest.php | 23 +++++++++++++++ 2 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 src/Subscription/Request/AddToBlacklistRequest.php diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php index 90377d1..0684720 100644 --- a/src/Subscription/Controller/BlacklistController.php +++ b/src/Subscription/Controller/BlacklistController.php @@ -10,6 +10,7 @@ use PhpList\Core\Security\Authentication; use PhpList\RestBundle\Common\Controller\BaseController; use PhpList\RestBundle\Common\Validator\RequestValidator; +use PhpList\RestBundle\Subscription\Request\AddToBlacklistRequest; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -144,20 +145,13 @@ public function addEmailToBlacklist(Request $request): JsonResponse throw $this->createAccessDeniedException('You are not allowed to add emails to blacklist.'); } - $data = json_decode($request->getContent(), true); - if (!isset($data['email']) || empty($data['email'])) { - return $this->json(['error' => 'Email is required'], Response::HTTP_UNPROCESSABLE_ENTITY); - } - - $email = $data['email']; - $reason = $data['reason'] ?? null; + /** @var AddToBlacklistRequest $definitionRequest */ + $definitionRequest = $this->validator->validate($request, AddToBlacklistRequest::class); - $this->blacklistManager->addEmailToBlacklist($email, $reason); + $userBlacklisted = $this->blacklistManager->addEmailToBlacklist($definitionRequest->email, $definitionRequest->reason); + $json = $this->normalizer->normalize($userBlacklisted, 'json'); - return $this->json([ - 'success' => true, - 'message' => sprintf('Email %s has been added to the blacklist', $email), - ], Response::HTTP_CREATED); + return $this->json($json, Response::HTTP_CREATED); } #[Route('/remove/{email}', name: 'remove', methods: ['DELETE'])] @@ -209,10 +203,7 @@ public function removeEmailFromBlacklist(Request $request, string $email): JsonR $this->blacklistManager->removeEmailFromBlacklist($email); - return $this->json([ - 'success' => true, - 'message' => sprintf('Email %s has been removed from the blacklist', $email), - ]); + return $this->json(null, Response::HTTP_NO_CONTENT); } #[Route('/info/{email}', name: 'info', methods: ['GET'])] @@ -270,14 +261,16 @@ public function getBlacklistInfo(Request $request, string $email): JsonResponse $blacklistInfo = $this->blacklistManager->getBlacklistInfo($email); if (!$blacklistInfo) { - return $this->json(['error' => sprintf('Email %s is not blacklisted', $email)], Response::HTTP_NOT_FOUND); + return $this->json([ + 'error' => sprintf('Email %s is not blacklisted', $email) + ], Response::HTTP_NOT_FOUND); } $reason = $this->blacklistManager->getBlacklistReason($email); return $this->json([ 'email' => $blacklistInfo->getEmail(), - 'added' => $blacklistInfo->getAdded() ? $blacklistInfo->getAdded()->format('c') : null, + 'added' => $blacklistInfo->getAdded()?->format('c'), 'reason' => $reason, ]); } diff --git a/src/Subscription/Request/AddToBlacklistRequest.php b/src/Subscription/Request/AddToBlacklistRequest.php new file mode 100644 index 0000000..68dc81e --- /dev/null +++ b/src/Subscription/Request/AddToBlacklistRequest.php @@ -0,0 +1,23 @@ + Date: Fri, 8 Aug 2025 14:09:43 +0400 Subject: [PATCH 3/8] UserBlacklistNormalizer --- config/services/normalizers.yml | 4 ++ .../Controller/BlacklistController.php | 4 ++ .../Serializer/UserBlacklistNormalizer.php | 42 ++++++++++++++ .../UserBlacklistNormalizerTest.php | 57 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/Subscription/Serializer/UserBlacklistNormalizer.php create mode 100644 tests/Unit/Subscription/Serializer/UserBlacklistNormalizerTest.php diff --git a/config/services/normalizers.yml b/config/services/normalizers.yml index 1d16cc3..a270a3f 100644 --- a/config/services/normalizers.yml +++ b/config/services/normalizers.yml @@ -93,3 +93,7 @@ services: PhpList\RestBundle\Statistics\Serializer\TopLocalPartsNormalizer: tags: [ 'serializer.normalizer' ] autowire: true + + PhpList\RestBundle\Subscription\Serializer\UserBlacklistNormalizer: + tags: [ 'serializer.normalizer' ] + autowire: true diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php index 0684720..a9128a2 100644 --- a/src/Subscription/Controller/BlacklistController.php +++ b/src/Subscription/Controller/BlacklistController.php @@ -11,6 +11,7 @@ use PhpList\RestBundle\Common\Controller\BaseController; use PhpList\RestBundle\Common\Validator\RequestValidator; use PhpList\RestBundle\Subscription\Request\AddToBlacklistRequest; +use PhpList\RestBundle\Subscription\Serializer\UserBlacklistNormalizer; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -23,15 +24,18 @@ class BlacklistController extends BaseController { private SubscriberBlacklistManager $blacklistManager; + private UserBlacklistNormalizer $normalizer; public function __construct( Authentication $authentication, RequestValidator $validator, SubscriberBlacklistManager $blacklistManager, + UserBlacklistNormalizer $normalizer, ) { parent::__construct($authentication, $validator); $this->authentication = $authentication; $this->blacklistManager = $blacklistManager; + $this->normalizer = $normalizer; } #[Route('/check/{email}', name: 'check', methods: ['GET'])] diff --git a/src/Subscription/Serializer/UserBlacklistNormalizer.php b/src/Subscription/Serializer/UserBlacklistNormalizer.php new file mode 100644 index 0000000..f8ff01c --- /dev/null +++ b/src/Subscription/Serializer/UserBlacklistNormalizer.php @@ -0,0 +1,42 @@ +blacklistManager->getBlacklistReason($object->getEmail()); + + return [ + 'email' => $object->getEmail(), + 'added' => $object->getAdded()?->format('Y-m-d\TH:i:sP'), + 'reason' => $reason, + ]; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function supportsNormalization($data, string $format = null): bool + { + return $data instanceof UserBlacklist; + } +} diff --git a/tests/Unit/Subscription/Serializer/UserBlacklistNormalizerTest.php b/tests/Unit/Subscription/Serializer/UserBlacklistNormalizerTest.php new file mode 100644 index 0000000..85619b6 --- /dev/null +++ b/tests/Unit/Subscription/Serializer/UserBlacklistNormalizerTest.php @@ -0,0 +1,57 @@ +createMock(SubscriberBlacklistManager::class); + $normalizer = new UserBlacklistNormalizer($blacklistManager); + $userBlacklist = $this->createMock(UserBlacklist::class); + + $this->assertTrue($normalizer->supportsNormalization($userBlacklist)); + $this->assertFalse($normalizer->supportsNormalization(new stdClass())); + } + + public function testNormalize(): void + { + $email = 'test@example.com'; + $added = new DateTime('2025-08-08T12:00:00+00:00'); + $reason = 'Unsubscribed by user'; + + $userBlacklist = $this->createMock(UserBlacklist::class); + $userBlacklist->method('getEmail')->willReturn($email); + $userBlacklist->method('getAdded')->willReturn($added); + + $blacklistManager = $this->createMock(SubscriberBlacklistManager::class); + $blacklistManager->method('getBlacklistReason')->with($email)->willReturn($reason); + + $normalizer = new UserBlacklistNormalizer($blacklistManager); + + $expected = [ + 'email' => $email, + 'added' => '2025-08-08T12:00:00+00:00', + 'reason' => $reason, + ]; + + $this->assertSame($expected, $normalizer->normalize($userBlacklist)); + } + + public function testNormalizeWithInvalidObject(): void + { + $blacklistManager = $this->createMock(SubscriberBlacklistManager::class); + $normalizer = new UserBlacklistNormalizer($blacklistManager); + + $this->assertSame([], $normalizer->normalize(new stdClass())); + } +} From 875172744354bac82f87229f3f3bda826a3fa1a2 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 8 Aug 2025 14:14:07 +0400 Subject: [PATCH 4/8] add in development flags --- .../Controller/BlacklistController.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php index a9128a2..5390f3c 100644 --- a/src/Subscription/Controller/BlacklistController.php +++ b/src/Subscription/Controller/BlacklistController.php @@ -41,7 +41,7 @@ public function __construct( #[Route('/check/{email}', name: 'check', methods: ['GET'])] #[OA\Get( path: '/api/v2/blacklist/check/{email}', - description: 'Checks if an email address is blacklisted.', + description: '🚧 **Status: Beta** – This method is under development. Avoid using in production.', summary: 'Check if email is blacklisted', tags: ['blacklist'], parameters: [ @@ -97,8 +97,8 @@ public function checkEmailBlacklisted(Request $request, string $email): JsonResp #[Route('/add', name: 'add', methods: ['POST'])] #[OA\Post( path: '/api/v2/blacklist/add', - description: 'Adds an email address to the blacklist.', - summary: 'Add email to blacklist', + description: '🚧 **Status: Beta** – This method is under development. Avoid using in production.', + summary: 'Adds an email address to the blacklist.', requestBody: new OA\RequestBody( description: 'Email to blacklist', required: true, @@ -161,8 +161,8 @@ public function addEmailToBlacklist(Request $request): JsonResponse #[Route('/remove/{email}', name: 'remove', methods: ['DELETE'])] #[OA\Delete( path: '/api/v2/blacklist/remove/{email}', - description: 'Removes an email address from the blacklist.', - summary: 'Remove email from blacklist', + description: '🚧 **Status: Beta** – This method is under development. Avoid using in production.', + summary: 'Removes an email address from the blacklist.', tags: ['blacklist'], parameters: [ new OA\Parameter( @@ -213,8 +213,8 @@ public function removeEmailFromBlacklist(Request $request, string $email): JsonR #[Route('/info/{email}', name: 'info', methods: ['GET'])] #[OA\Get( path: '/api/v2/blacklist/info/{email}', - description: 'Gets detailed information about a blacklisted email.', - summary: 'Get blacklist information', + description: '🚧 **Status: Beta** – This method is under development. Avoid using in production.', + summary: 'Gets detailed information about a blacklisted email.', tags: ['blacklist'], parameters: [ new OA\Parameter( From 7049b0fc9bf070d6295b4ae4b5f78b457c2af43d Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 8 Aug 2025 14:19:28 +0400 Subject: [PATCH 5/8] phpcs fix --- src/Subscription/Controller/BlacklistController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php index 5390f3c..62a4fd9 100644 --- a/src/Subscription/Controller/BlacklistController.php +++ b/src/Subscription/Controller/BlacklistController.php @@ -152,7 +152,10 @@ public function addEmailToBlacklist(Request $request): JsonResponse /** @var AddToBlacklistRequest $definitionRequest */ $definitionRequest = $this->validator->validate($request, AddToBlacklistRequest::class); - $userBlacklisted = $this->blacklistManager->addEmailToBlacklist($definitionRequest->email, $definitionRequest->reason); + $userBlacklisted = $this->blacklistManager->addEmailToBlacklist( + email: $definitionRequest->email, + reasonData: $definitionRequest->reason + ); $json = $this->normalizer->normalize($userBlacklisted, 'json'); return $this->json($json, Response::HTTP_CREATED); From 8ca0fb78cc2f473397cf43fa2493da038fbf3ca0 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 8 Aug 2025 14:22:29 +0400 Subject: [PATCH 6/8] use blacklist branch --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5b8e2a7..993a68b 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "require": { "php": "^8.1", - "phplist/core": "dev-main", + "phplist/core": "dev-blacklist", "friendsofsymfony/rest-bundle": "*", "symfony/test-pack": "^1.0", "symfony/process": "^6.4", From 08f77a178c82fddb7943b84df3ce9091e52734c0 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 8 Aug 2025 14:25:34 +0400 Subject: [PATCH 7/8] fix swagger --- src/Subscription/Controller/BlacklistController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Subscription/Controller/BlacklistController.php b/src/Subscription/Controller/BlacklistController.php index 62a4fd9..f76b094 100644 --- a/src/Subscription/Controller/BlacklistController.php +++ b/src/Subscription/Controller/BlacklistController.php @@ -255,7 +255,7 @@ public function removeEmailFromBlacklist(Request $request, string $email): JsonR new OA\Response( response: 404, description: 'Failure', - content: new OA\JsonContent(ref: '#/components/schemas/NotFoundResponse') + content: new OA\JsonContent(ref: '#/components/schemas/BadRequestResponse') ), ] )] From 73179aa9333de133b59ef0a1b8e8dfc3a47c0afe Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 8 Aug 2025 22:25:31 +0400 Subject: [PATCH 8/8] use core dev branch --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 993a68b..ba5526c 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "require": { "php": "^8.1", - "phplist/core": "dev-blacklist", + "phplist/core": "dev-dev", "friendsofsymfony/rest-bundle": "*", "symfony/test-pack": "^1.0", "symfony/process": "^6.4",