From ed49c2c380ca5eb4b0fd4d4c81c6f7b111a628e3 Mon Sep 17 00:00:00 2001 From: Maxime Veber Date: Mon, 28 Oct 2024 00:27:01 +0100 Subject: [PATCH 1/2] :sparkles: Add OkNoContent response --- features/crud.feature | 18 ++++--- features/custom_responses.feature | 9 +++- src/Response/OkNoContent.php | 18 +++++++ tests/Behat/Context/AbstractContext.php | 7 ++- tests/Behat/Context/BasicsContext.php | 50 +++++++++++++------ tests/TestApplication/config/config.yaml | 2 + .../TestApplication/config/documentation.yaml | 18 ++++++- tests/TestApplication/config/routing.yaml | 5 ++ .../src/Controller/ArchiveTodoAction.php | 20 ++++++++ tests/TestApplication/src/Entity/Todo.php | 21 ++++++-- 10 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 src/Response/OkNoContent.php create mode 100644 tests/TestApplication/src/Controller/ArchiveTodoAction.php diff --git a/features/crud.feature b/features/crud.feature index df65a09..7fa26db 100644 --- a/features/crud.feature +++ b/features/crud.feature @@ -25,17 +25,20 @@ Feature: { "id": 1, "content": "foo", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false }, { "id": 2, "content": "bar", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false }, { "id": 3, "content": "baz", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false } ] } @@ -61,7 +64,8 @@ Feature: { "id": 1, "content": "hello", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false } """ @@ -87,12 +91,14 @@ Feature: { "id": 2, "content": "bar", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false }, { "id": 3, "content": "baz", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false } ] } diff --git a/features/custom_responses.feature b/features/custom_responses.feature index 68cb51b..baa7ac4 100644 --- a/features/custom_responses.feature +++ b/features/custom_responses.feature @@ -24,7 +24,8 @@ Feature: { "id": 1, "content": "foo", - "publishDate":"2050-01-02T00:00:00+00:00" + "publishDate":"2050-01-02T00:00:00+00:00", + "archived": false } ] } @@ -33,3 +34,9 @@ Feature: Scenario: In debug mode, when an exception occurred I should get a response with stacktrace When I make a GET request on "/error" Then I should retrieve a stacktrace formatted in JSON + + Scenario: return ok content response + Given there are some todos + And I make a PATCH request on "/todos/1/archive" + Then the last response is empty + And the http code is 204 diff --git a/src/Response/OkNoContent.php b/src/Response/OkNoContent.php new file mode 100644 index 0000000..1b7a54a --- /dev/null +++ b/src/Response/OkNoContent.php @@ -0,0 +1,18 @@ +kernel->getContainer(); } - final protected function request(string $uri, string $verb, $rawContent): Response + final protected function request(string $uri, string $verb, ?string $rawContent): Response { $client = $this->getContainer()->get('test.client'); @@ -42,10 +42,9 @@ final protected function request(string $uri, string $verb, $rawContent): Respon return self::$response = $client->getResponse(); } - if (null === $rawContent) { - throw new \Exception(sprintf('Cannot process request "%s" with no content.', $verb)); + if (null !== $rawContent) { + json_decode($rawContent, true, 512, \JSON_THROW_ON_ERROR); } - json_decode($rawContent, true, 512, \JSON_THROW_ON_ERROR); $client->request($verb, $uri, [], [], [], $rawContent); diff --git a/tests/Behat/Context/BasicsContext.php b/tests/Behat/Context/BasicsContext.php index 9fe92c9..37ab648 100644 --- a/tests/Behat/Context/BasicsContext.php +++ b/tests/Behat/Context/BasicsContext.php @@ -11,13 +11,29 @@ class BasicsContext extends AbstractContext { /** - * @When I make a GET request on :uri + * @BeforeScenario + */ + public function resetDb() + { + @unlink(dirname(__DIR__) . '/../TestApplication/var/data.db'); + + $entityManager = $this->getContainer()->get('doctrine')->getManager(); + $metadatas = $entityManager->getMetadataFactory()->getAllMetadata(); + $schemaTool = new SchemaTool($entityManager); + $schemaTool->createSchema($metadatas); + } + + /** + * @When I make a :verb request on :uri * * @Given I make a :verb request on :uri with the content: */ public function iMakeARequestOn($uri, $verb = 'GET', ?PyStringNode $content = null) { - $this->request($uri, $verb, (string) $content); + if ($content instanceof PyStringNode) { + $content = (string) $content; + } + $this->request($uri, $verb, $content); } /** @@ -48,19 +64,6 @@ public function iShouldRetrieve(PyStringNode $string) } } - /** - * @BeforeScenario - */ - public function resetDb() - { - @unlink(dirname(__DIR__) . '/../TestApplication/var/data.db'); - - $entityManager = $this->getContainer()->get('doctrine')->getManager(); - $metadatas = $entityManager->getMetadataFactory()->getAllMetadata(); - $schemaTool = new SchemaTool($entityManager); - $schemaTool->createSchema($metadatas); - } - /** * @Then I should retrieve a stacktrace formatted in JSON */ @@ -73,4 +76,21 @@ public function iShouldRetrieveAStacktraceFormattedInJson() Assert::keyExists($content, 'detail'); Assert::keyExists($content, 'trace'); } + + /** + * @Then the last response is empty + */ + public function theLastResponseIsEmpty() + { + $responseContent = $this->getLastResponse()->getContent(); + Assert::isEmpty($responseContent); + } + + /** + * @Then the http code is :httpCode + */ + public function theHttpCodeIs($httpCode) + { + Assert::same($this->getLastResponse()->getStatusCode(), (int) $httpCode); + } } diff --git a/tests/TestApplication/config/config.yaml b/tests/TestApplication/config/config.yaml index f134205..bd794b8 100644 --- a/tests/TestApplication/config/config.yaml +++ b/tests/TestApplication/config/config.yaml @@ -52,6 +52,8 @@ services: SwagIndustries\Melodiia\Tests\Behat\: resource: '../../Behat/*' + TestApplication\Controller\ArchiveTodoAction: + tags: ['controller.service_arguments'] TestApplication\Controller\TodoContainsAction: tags: ['controller.service_arguments'] TestApplication\Controller\SimulateExceptionAction: diff --git a/tests/TestApplication/config/documentation.yaml b/tests/TestApplication/config/documentation.yaml index 4c2b15b..35ecf77 100644 --- a/tests/TestApplication/config/documentation.yaml +++ b/tests/TestApplication/config/documentation.yaml @@ -50,7 +50,23 @@ paths: 400: description: Invalid input content: {} - + /todos/{todoId}/archive: + patch: + tags: + - todo + summary: Archive a todo + operationId: archiveTodo + parameters: + - name: todoId + in: path + description: ID of the todo to archive + required: true + schema: + type: integer + format: int64 + responses: + 204: + description: Todo will be archived components: schemas: Todo: diff --git a/tests/TestApplication/config/routing.yaml b/tests/TestApplication/config/routing.yaml index c06478a..63a5757 100644 --- a/tests/TestApplication/config/routing.yaml +++ b/tests/TestApplication/config/routing.yaml @@ -10,6 +10,11 @@ get_todo_contains: methods: GET controller: 'TestApplication\Controller\TodoContainsAction' +archive_todo: + path: /todos/{id}/archive + methods: PATCH + controller: 'TestApplication\Controller\ArchiveTodoAction' + get_todo: path: /todos/{id} controller: 'melodiia.crud.controller.get' diff --git a/tests/TestApplication/src/Controller/ArchiveTodoAction.php b/tests/TestApplication/src/Controller/ArchiveTodoAction.php new file mode 100644 index 0000000..3d7445a --- /dev/null +++ b/tests/TestApplication/src/Controller/ArchiveTodoAction.php @@ -0,0 +1,20 @@ +remove($todo); + $entityManager->flush(); + + return new OkNoContent(); + } +} diff --git a/tests/TestApplication/src/Entity/Todo.php b/tests/TestApplication/src/Entity/Todo.php index 6a8b682..215f340 100644 --- a/tests/TestApplication/src/Entity/Todo.php +++ b/tests/TestApplication/src/Entity/Todo.php @@ -13,13 +13,16 @@ class Todo implements MelodiiaModel #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] - private $id; + private ?int $id = null; #[ORM\Column(type: 'text')] - private $content; + private ?string $content; #[ORM\Column(type: 'datetime_immutable')] - private $publishDate; + private ?\DateTimeImmutable $publishDate; + + #[ORM\Column(type: 'boolean')] + private bool $archived = false; public function getId(): ?int { @@ -36,7 +39,7 @@ public function setContent($content): void $this->content = $content; } - public function getPublishDate() + public function getPublishDate(): ?\DateTimeImmutable { return $this->publishDate; } @@ -45,4 +48,14 @@ public function setPublishDate(?\DateTimeImmutable $publishDate): void { $this->publishDate = $publishDate; } + + public function isArchived(): bool + { + return $this->archived; + } + + public function archive(): void + { + $this->archived = true; + } } From 2fcccc86688cc2b19fdc2bdae03b83c625c212db Mon Sep 17 00:00:00 2001 From: Maxime Veber Date: Mon, 28 Oct 2024 00:27:28 +0100 Subject: [PATCH 2/2] :lipstick: Code style fixes --- composer.json | 2 +- src/Crud/Controller/GetAll.php | 2 +- src/EventListener/ExceptionListener.php | 2 +- tests/Melodiia/Crud/FilterCollectionFactoryTest.php | 2 +- tests/Melodiia/EventListener/ExceptionListenerTest.php | 2 +- tests/Melodiia/Form/ApiTypeTest.php | 2 +- .../Listener/ReorderDataToMatchCollectionListenerTest.php | 6 +++--- .../Response/Listener/SerializeOnKernelViewTest.php | 2 +- .../Serialization/Context/ContextBuilderFactoryTest.php | 6 +++--- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index c458be9..18802be 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "roave/security-advisories": "dev-latest", "psr/container": "^v1.1.1 || ^2.0", "phpunit/phpunit": "^9.6 || ^10.0.0 || ^11.1.3", - "friendsofphp/php-cs-fixer": "3.58.0", + "friendsofphp/php-cs-fixer": "3.64.0", "phpspec/prophecy": "^1.19", "twig/twig": "^2.5 || ^3.10", "doctrine/orm": "^2.6.6 || ^2.17.0", diff --git a/src/Crud/Controller/GetAll.php b/src/Crud/Controller/GetAll.php index 8d7b9de..9553124 100644 --- a/src/Crud/Controller/GetAll.php +++ b/src/Crud/Controller/GetAll.php @@ -33,7 +33,7 @@ public function __construct( DataStoreInterface $dataStore, FilterCollectionFactoryInterface $collectionFactory, PaginationRequestFactoryInterface $pagesRequestFactory, - ?AuthorizationCheckerInterface $checker = null + ?AuthorizationCheckerInterface $checker = null, ) { $this->dataStore = $dataStore; $this->checker = $checker; diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php index 0a212d7..77a8228 100644 --- a/src/EventListener/ExceptionListener.php +++ b/src/EventListener/ExceptionListener.php @@ -21,7 +21,7 @@ public function __construct( MelodiiaConfigurationInterface $config, OnError $controller, bool $debug, - ?ErrorListener $errorListener = null + ?ErrorListener $errorListener = null, ) { $this->errorListener = $errorListener ?? new ErrorListener($controller, null, $debug); $this->config = $config; diff --git a/tests/Melodiia/Crud/FilterCollectionFactoryTest.php b/tests/Melodiia/Crud/FilterCollectionFactoryTest.php index 92832b8..1a42de4 100644 --- a/tests/Melodiia/Crud/FilterCollectionFactoryTest.php +++ b/tests/Melodiia/Crud/FilterCollectionFactoryTest.php @@ -29,7 +29,7 @@ public function setUp(): void public function testItCreatesCollection() { - $subject = new FilterCollectionFactory($this->formFactory->reveal(), [new class() implements FilterInterface { + $subject = new FilterCollectionFactory($this->formFactory->reveal(), [new class implements FilterInterface { public function filter($queryBuilder, FormInterface $form): void { /* do nothing */ } diff --git a/tests/Melodiia/EventListener/ExceptionListenerTest.php b/tests/Melodiia/EventListener/ExceptionListenerTest.php index a839696..b7b78fc 100644 --- a/tests/Melodiia/EventListener/ExceptionListenerTest.php +++ b/tests/Melodiia/EventListener/ExceptionListenerTest.php @@ -67,7 +67,7 @@ public function testItHandleExceptionIfItsUnderAMelodiiaRoute() private function fakeErrorListener() { - return new class() extends ErrorListener { + return new class extends ErrorListener { private $isCalled = false; public function __construct() diff --git a/tests/Melodiia/Form/ApiTypeTest.php b/tests/Melodiia/Form/ApiTypeTest.php index b3ac33f..c092d9e 100644 --- a/tests/Melodiia/Form/ApiTypeTest.php +++ b/tests/Melodiia/Form/ApiTypeTest.php @@ -39,7 +39,7 @@ public function testItReturnsDataForEmptyDataInCaseOfDataAvailable() public function testItSupportsCustomDataMapper() { - $customDataMapper = new class() extends DomainObjectsDataMapper { + $customDataMapper = new class extends DomainObjectsDataMapper { private $hasBeenCalled = false; public function createObject(iterable $form, ?string $dataClass = null) diff --git a/tests/Melodiia/Form/Listener/ReorderDataToMatchCollectionListenerTest.php b/tests/Melodiia/Form/Listener/ReorderDataToMatchCollectionListenerTest.php index f4e4aa9..2bc9f6d 100644 --- a/tests/Melodiia/Form/Listener/ReorderDataToMatchCollectionListenerTest.php +++ b/tests/Melodiia/Form/Listener/ReorderDataToMatchCollectionListenerTest.php @@ -65,7 +65,7 @@ protected function getForm($name = 'name', $data = null) public function testItReorderDataInputWithFormData() { $formData = [ - new class() extends \ArrayObject implements MelodiiaModel { + new class extends \ArrayObject implements MelodiiaModel { public function __construct() { parent::__construct(['hello' => 'yo', 'world' => 'ye']); @@ -79,7 +79,7 @@ public function getId() return 'foo'; } }, - new class() extends \ArrayObject implements MelodiiaModel { + new class extends \ArrayObject implements MelodiiaModel { public function __construct() { parent::__construct(['hello' => 'yoh', 'world' => 'yeh']); @@ -113,7 +113,7 @@ public function getId() public function testItRemovesDataThatDoesNotExistsAnymore() { $formData = [ - new class() extends \ArrayObject implements MelodiiaModel { + new class extends \ArrayObject implements MelodiiaModel { public function __construct() { parent::__construct(['hello' => 'yo', 'world' => 'ye']); diff --git a/tests/Melodiia/Response/Listener/SerializeOnKernelViewTest.php b/tests/Melodiia/Response/Listener/SerializeOnKernelViewTest.php index 472902a..0f3648a 100644 --- a/tests/Melodiia/Response/Listener/SerializeOnKernelViewTest.php +++ b/tests/Melodiia/Response/Listener/SerializeOnKernelViewTest.php @@ -42,7 +42,7 @@ public function setUp(): void $this->contextChain = $this->prophesize(ContextBuilderChainInterface::class); $this->contextChain->buildContext(Argument::cetera())->willReturn([]); $this->listener = new SerializeOnKernelView($this->serializer->reveal(), $this->contextChain->reveal()); - $this->dummyResponse = new class() implements ApiResponse { + $this->dummyResponse = new class implements ApiResponse { public function httpStatus(): int { return 200; diff --git a/tests/Melodiia/Serialization/Context/ContextBuilderFactoryTest.php b/tests/Melodiia/Serialization/Context/ContextBuilderFactoryTest.php index 397eb88..2423210 100644 --- a/tests/Melodiia/Serialization/Context/ContextBuilderFactoryTest.php +++ b/tests/Melodiia/Serialization/Context/ContextBuilderFactoryTest.php @@ -16,7 +16,7 @@ class ContextBuilderFactoryTest extends TestCase public function testItBuildContextUsingGivenBuilders() { - $builder1 = new class() implements ContextBuilderInterface { + $builder1 = new class implements ContextBuilderInterface { public function buildContext(array $context, ApiResponse $response): array { $context['foo'] = true; @@ -29,7 +29,7 @@ public function supports(ApiResponse $response): bool return true; } }; - $builder2 = new class() implements ContextBuilderInterface { + $builder2 = new class implements ContextBuilderInterface { public function buildContext(array $context, ApiResponse $response): array { $context['baz'] = true; @@ -42,7 +42,7 @@ public function supports(ApiResponse $response): bool return false; } }; - $builder3 = new class() implements ContextBuilderInterface { + $builder3 = new class implements ContextBuilderInterface { public function buildContext(array $context, ApiResponse $response): array { $context['bar'] = true;