diff --git a/src/Exception/InvalidDomainModelArgumentException.php b/src/Exception/InvalidDomainModelArgumentException.php index 90a0268..fa9d8b8 100644 --- a/src/Exception/InvalidDomainModelArgumentException.php +++ b/src/Exception/InvalidDomainModelArgumentException.php @@ -23,15 +23,18 @@ public static function forInconsistentNumberOfCommands( public static function forInconsistentNumbersOfCommandProperties( int $numberOfCommands, $numberOfSuccessfulCommands, - $numberOfFailedCommands + $numberOfFailedCommands, + $numberOfSkippedCommands ): self { return new self( sprintf( 'Provided numberOfCommands (%d) is inconsistent with provided sum of ' - . 'numberOfSuccessfulCommands (%d) and numberOfFailedCommands (%d)', + . 'numberOfSuccessfulCommands (%d) + numberOfFailedCommands (%d)' + . ' + numberOfSkippedCommands (%d)', $numberOfCommands, $numberOfSuccessfulCommands, - $numberOfFailedCommands + $numberOfFailedCommands, + $numberOfSkippedCommands ) ); } diff --git a/src/Exception/ResponseDecodingException.php b/src/Exception/ResponseDecodingException.php index 18eb97c..3ff3eaa 100644 --- a/src/Exception/ResponseDecodingException.php +++ b/src/Exception/ResponseDecodingException.php @@ -21,4 +21,14 @@ public static function forJsonError(string $jsonErrorMsg, ResponseInterface $res ) ); } + + public static function forInvalidData(ResponseInterface $response): self + { + return new self( + sprintf( + "Error decoding Matej response: required data missing.\n\nBody:\n%s", + $response->getBody() + ) + ); + } } diff --git a/src/Http/Plugin/ExceptionPlugin.php b/src/Http/Plugin/ExceptionPlugin.php index 1d89856..23cb197 100644 --- a/src/Http/Plugin/ExceptionPlugin.php +++ b/src/Http/Plugin/ExceptionPlugin.php @@ -30,7 +30,7 @@ private function transformResponseToException(RequestInterface $request, Respons throw AuthorizationException::fromRequestAndResponse($request, $response); } - if ($responseCode >= 401 && $responseCode < 600) { + if ($responseCode >= 400 && $responseCode < 600) { // TODO: use more specific exceptions throw new RequestException($response->getReasonPhrase(), $request, $response); } diff --git a/src/Http/ResponseDecoder.php b/src/Http/ResponseDecoder.php index 8d559dc..b4b59ca 100644 --- a/src/Http/ResponseDecoder.php +++ b/src/Http/ResponseDecoder.php @@ -16,11 +16,30 @@ public function decode(ResponseInterface $httpResponse): Response throw ResponseDecodingException::forJsonError(json_last_error_msg(), $httpResponse); } + if (!$this->isResponseValid($responseData)) { + throw ResponseDecodingException::forInvalidData($httpResponse); + } + return new Response( (int) $responseData->commands->number_of_commands, (int) $responseData->commands->number_of_successful_commands, (int) $responseData->commands->number_of_failed_commands, + (int) $responseData->commands->number_of_skipped_commands, $responseData->commands->responses ); } + + private function isResponseValid(\stdClass $responseData): bool + { + return isset( + $responseData->commands, + $responseData->commands->number_of_commands, + $responseData->commands->number_of_successful_commands, + $responseData->commands->number_of_failed_commands, + $responseData->commands->number_of_skipped_commands, + $responseData->commands->responses, + $responseData->message, + $responseData->status + ); + } } diff --git a/src/Model/Response.php b/src/Model/Response.php index 49f65ab..2179dc0 100644 --- a/src/Model/Response.php +++ b/src/Model/Response.php @@ -14,16 +14,20 @@ class Response private $numberOfSuccessfulCommands; /** @var int */ private $numberOfFailedCommands; + /** @var int */ + private $numberOfSkippedCommands; public function __construct( int $numberOfCommands, int $numberOfSuccessfulCommands, int $numberOfFailedCommands, + int $numberOfSkippedCommands, array $commandResponses = [] ) { $this->numberOfCommands = $numberOfCommands; $this->numberOfSuccessfulCommands = $numberOfSuccessfulCommands; $this->numberOfFailedCommands = $numberOfFailedCommands; + $this->numberOfSkippedCommands = $numberOfSkippedCommands; foreach ($commandResponses as $rawCommandResponse) { $this->commandResponses[] = CommandResponse::createFromRawCommandResponseObject($rawCommandResponse); @@ -36,11 +40,15 @@ public function __construct( ); } - if ($this->numberOfCommands !== ($this->numberOfSuccessfulCommands + $this->numberOfFailedCommands)) { + $commandSum = $this->numberOfSuccessfulCommands + $this->numberOfFailedCommands + + $this->numberOfSkippedCommands; + + if ($this->numberOfCommands !== $commandSum) { throw InvalidDomainModelArgumentException::forInconsistentNumbersOfCommandProperties( $this->numberOfCommands, $this->numberOfSuccessfulCommands, - $this->numberOfFailedCommands + $this->numberOfFailedCommands, + $this->numberOfSkippedCommands ); } } @@ -60,6 +68,11 @@ public function getNumberOfFailedCommands(): int return $this->numberOfFailedCommands; } + public function getNumberOfSkippedCommands(): int + { + return $this->numberOfSkippedCommands; + } + /** * Each Command which was part of request batch has here corresponding CommandResponse - on the same index on which * the Command was added to the request batch. diff --git a/tests/Http/Fixtures/invalid-response-format.json b/tests/Http/Fixtures/invalid-response-format.json new file mode 100644 index 0000000..ddeda69 --- /dev/null +++ b/tests/Http/Fixtures/invalid-response-format.json @@ -0,0 +1,5 @@ +{ + "invalid": [], + "message": "", + "status": "OK" +} diff --git a/tests/Http/Fixtures/response-item-properties.json b/tests/Http/Fixtures/response-item-properties.json index 9fdf3c0..24d975e 100644 --- a/tests/Http/Fixtures/response-item-properties.json +++ b/tests/Http/Fixtures/response-item-properties.json @@ -2,6 +2,7 @@ "commands": { "number_of_commands": 3, "number_of_failed_commands": 1, + "number_of_skipped_commands": 0, "number_of_successful_commands": 2, "responses": [ { diff --git a/tests/Http/Fixtures/response-one-successful-command.json b/tests/Http/Fixtures/response-one-successful-command.json index 985b1b1..7a0c501 100644 --- a/tests/Http/Fixtures/response-one-successful-command.json +++ b/tests/Http/Fixtures/response-one-successful-command.json @@ -2,6 +2,7 @@ "commands": { "number_of_commands": 1, "number_of_failed_commands": 0, + "number_of_skipped_commands": 0, "number_of_successful_commands": 1, "responses": [ { diff --git a/tests/Http/Plugin/ExceptionPluginTest.php b/tests/Http/Plugin/ExceptionPluginTest.php index 99ededf..334d4e4 100644 --- a/tests/Http/Plugin/ExceptionPluginTest.php +++ b/tests/Http/Plugin/ExceptionPluginTest.php @@ -42,9 +42,6 @@ public function provideSuccessStatusCodes(): array return [ 'HTTP 200' => [StatusCodeInterface::STATUS_OK], 'HTTP 201' => [StatusCodeInterface::STATUS_CREATED], - 'HTTP 400 (batch was successfully executed, but all commands were rejected)' => [ - StatusCodeInterface::STATUS_BAD_REQUEST, - ], ]; } @@ -75,6 +72,7 @@ public function shouldThrowExceptionBasedOnStatusCode(int $statusCode, string $e public function provideErrorStatusCodes(): array { return [ + 'HTTP 401' => [StatusCodeInterface::STATUS_BAD_REQUEST, AuthorizationException::class], 'HTTP 401' => [StatusCodeInterface::STATUS_UNAUTHORIZED, AuthorizationException::class], 'HTTP 404' => [StatusCodeInterface::STATUS_NOT_FOUND, RequestException::class], 'HTTP 500' => [StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, RequestException::class], diff --git a/tests/Http/ResponseDecoderTest.php b/tests/Http/ResponseDecoderTest.php index 416c574..ed4ffcb 100644 --- a/tests/Http/ResponseDecoderTest.php +++ b/tests/Http/ResponseDecoderTest.php @@ -31,6 +31,7 @@ public function shouldDecodeSimpleOkResponse(): void $this->assertSame(1, $output->getNumberOfCommands()); $this->assertSame(1, $output->getNumberOfSuccessfulCommands()); $this->assertSame(0, $output->getNumberOfFailedCommands()); + $this->assertSame(0, $output->getNumberOfSkippedCommands()); $commandResponses = $output->getCommandResponses(); $this->assertCount(1, $commandResponses); @@ -48,6 +49,7 @@ public function shouldDecodeResponseMultipleResponses(): void $this->assertSame(3, $output->getNumberOfCommands()); $this->assertSame(2, $output->getNumberOfSuccessfulCommands()); $this->assertSame(1, $output->getNumberOfFailedCommands()); + $this->assertSame(0, $output->getNumberOfSkippedCommands()); $commandResponses = $output->getCommandResponses(); $this->assertCount(3, $commandResponses); @@ -70,6 +72,18 @@ public function shouldThrowExceptionWhenDecodingFails(): void $this->decoder->decode($response); } + /** @test */ + public function shouldThrowExceptionWhenJsonWithInvalidDataIsDecoded(): void + { + $notJsonData = file_get_contents(__DIR__ . '/Fixtures/invalid-response-format.json'); + $response = new Response(StatusCodeInterface::STATUS_NOT_FOUND, [], $notJsonData); + + $this->expectException(ResponseDecodingException::class); + $this->expectExceptionMessage('Error decoding Matej response: required data missing.'); + $this->expectExceptionMessage('"invalid": [],'); + $this->decoder->decode($response); + } + private function createJsonResponseFromFile(string $fileName): ResponseInterface { $jsonData = file_get_contents($fileName); diff --git a/tests/Model/ResponseTest.php b/tests/Model/ResponseTest.php index 8636456..4cb0f43 100644 --- a/tests/Model/ResponseTest.php +++ b/tests/Model/ResponseTest.php @@ -14,20 +14,23 @@ class ResponseTest extends TestCase */ public function shouldBeInstantiable( int $numberOfCommands, - int $numberOfSuccessfulCommands, - int $numberOfFailedCommands, + int $numberOfSuccessful, + int $numberOfFailed, + int $numberOfSkipped, array $commandResponses ): void { $response = new Response( $numberOfCommands, - $numberOfSuccessfulCommands, - $numberOfFailedCommands, + $numberOfSuccessful, + $numberOfFailed, + $numberOfSkipped, $commandResponses ); $this->assertSame($numberOfCommands, $response->getNumberOfCommands()); - $this->assertSame($numberOfSuccessfulCommands, $response->getNumberOfSuccessfulCommands()); - $this->assertSame($numberOfFailedCommands, $response->getNumberOfFailedCommands()); + $this->assertSame($numberOfSuccessful, $response->getNumberOfSuccessfulCommands()); + $this->assertSame($numberOfFailed, $response->getNumberOfFailedCommands()); + $this->assertSame($numberOfSkipped, $response->getNumberOfSkippedCommands()); $this->assertContainsOnlyInstancesOf(CommandResponse::class, $response->getCommandResponses()); $this->assertCount(count($commandResponses), $response->getCommandResponses()); @@ -40,19 +43,23 @@ public function provideResponseData(): array { $okCommandResponse = (object) ['status' => CommandResponse::STATUS_OK, 'message' => '', 'data' => []]; $failedCommandResponse = (object) ['status' => CommandResponse::STATUS_ERROR, 'message' => 'KO', 'data' => []]; + $skippedCommandResponse = (object) ['status' => CommandResponse::STATUS_SKIPPED, 'message' => '', 'data' => []]; return [ - 'empty response data' => [0, 0, 0, []], - 'multiple successful commands' => [2, 2, 0, [$okCommandResponse, $okCommandResponse]], - 'multiple failed commands' => [2, 0, 2, [$failedCommandResponse, $failedCommandResponse]], - 'multiple failed and successful commands' => [ - 4, + 'empty response data' => [0, 0, 0, 0, []], + 'multiple successful commands' => [2, 2, 0, 0, [$okCommandResponse, $okCommandResponse]], + 'multiple failed commands' => [2, 0, 2, 0, [$failedCommandResponse, $failedCommandResponse]], + 'multiple skipped commands' => [2, 0, 0, 2, [$skippedCommandResponse, $skippedCommandResponse]], + 'multiple successful , failed and skipped commands' => [ + 5, 2, 2, + 1, [ $failedCommandResponse, $okCommandResponse, $okCommandResponse, + $skippedCommandResponse, $failedCommandResponse, ], ], @@ -65,15 +72,16 @@ public function provideResponseData(): array */ public function shouldThrowExceptionWhenInconsistentDataProvided( int $numberOfCommands, - int $numberOfSuccessfulCommands, - int $numberOfFailedCommands, + int $numberOfSuccessful, + int $numberOfFailed, + int $numberOfSkipped, array $commandResponses, string $expectedExceptionMessage ): void { $this->expectException(InvalidDomainModelArgumentException::class); $this->expectExceptionMessage($expectedExceptionMessage); - new Response($numberOfCommands, $numberOfSuccessfulCommands, $numberOfFailedCommands, $commandResponses); + new Response($numberOfCommands, $numberOfSuccessful, $numberOfFailed, $numberOfSkipped, $commandResponses); } /** @@ -88,6 +96,7 @@ public function provideInconsistentData(): array 5, 0, 0, + 0, [], 'Provided numberOfCommands (5) is inconsistent with actual count of command responses (0)', ], @@ -95,6 +104,7 @@ public function provideInconsistentData(): array 0, 0, 0, + 0, [$commandResponse, $commandResponse], 'Provided numberOfCommands (0) is inconsistent with actual count of command responses (2)', ], @@ -102,9 +112,10 @@ public function provideInconsistentData(): array 2, 2, 1, + 1, [$commandResponse, $commandResponse], 'Provided numberOfCommands (2) is inconsistent with provided sum of numberOfSuccessfulCommands (2)' - . ' and numberOfFailedCommands (1)', + . ' + numberOfFailedCommands (1) + numberOfSkippedCommands (1)', ], ]; }