Skip to content

Commit

Permalink
Merge ebd89a6 into 7262288
Browse files Browse the repository at this point in the history
  • Loading branch information
OndraM committed Nov 15, 2017
2 parents 7262288 + ebd89a6 commit f98d085
Show file tree
Hide file tree
Showing 15 changed files with 540 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/Exception/AuthorizationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
class AuthorizationException extends RequestException
{
public static function createFromRequestAndResponse(
public static function fromRequestAndResponse(
RequestInterface $request,
ResponseInterface $response,
\Throwable $previous = null
Expand Down
38 changes: 38 additions & 0 deletions src/Exception/InvalidDomainModelArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Exception;

/**
* Exception thrown when invalid argument is passed while creating domain model.
*/
class InvalidDomainModelArgumentException extends AbstractMatejException
{
public static function forInconsistentNumberOfCommands(
int $numberOfCommands,
int $commandResponsesCount
): self {
return new self(
sprintf(
'Provided numberOfCommands (%d) is inconsistent with actual count of command responses (%d)',
$numberOfCommands,
$commandResponsesCount
)
);
}

public static function forInconsistentNumbersOfCommandProperties(
int $numberOfCommands,
$numberOfSuccessfulCommands,
$numberOfFailedCommands
): self {
return new self(
sprintf(
'Provided numberOfCommands (%d) is inconsistent with provided sum of '
. 'numberOfSuccessfulCommands (%d) and numberOfFailedCommands (%d)',
$numberOfCommands,
$numberOfSuccessfulCommands,
$numberOfFailedCommands
)
);
}
}
24 changes: 24 additions & 0 deletions src/Exception/ResponseDecodingException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Exception;

use Psr\Http\Message\ResponseInterface;

/**
* Exception thrown when Matej response cannot be decoded.
*/
class ResponseDecodingException extends AbstractMatejException
{
public static function forJsonError(string $jsonErrorMsg, ResponseInterface $response): self
{
return new self(
sprintf(
"Error decoding Matej response: %s\n\nStatus code: %s %s\nBody:\n%s",
$jsonErrorMsg,
$response->getStatusCode(),
$response->getReasonPhrase(),
$response->getBody()
)
);
}
}
2 changes: 1 addition & 1 deletion src/Http/Plugin/ExceptionPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private function transformResponseToException(RequestInterface $request, Respons
$responseCode = $response->getStatusCode();

if ($responseCode === StatusCodeInterface::STATUS_UNAUTHORIZED) {
throw AuthorizationException::createFromRequestAndResponse($request, $response);
throw AuthorizationException::fromRequestAndResponse($request, $response);
}

if ($responseCode >= 401 && $responseCode < 600) {
Expand Down
26 changes: 26 additions & 0 deletions src/Http/ResponseDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Http;

use Lmc\Matej\Exception\ResponseDecodingException;
use Lmc\Matej\Model\Response;
use Psr\Http\Message\ResponseInterface;

class ResponseDecoder implements ResponseDecoderInterface
{
public function decode(ResponseInterface $httpResponse): Response
{
$responseData = json_decode($httpResponse->getBody()->getContents());

if (JSON_ERROR_NONE !== json_last_error()) {
throw ResponseDecodingException::forJsonError(json_last_error_msg(), $httpResponse);
}

return new Response(
(int) $responseData->commands->number_of_commands,
(int) $responseData->commands->number_of_successful_commands,
(int) $responseData->commands->number_of_failed_commands,
$responseData->commands->responses
);
}
}
12 changes: 12 additions & 0 deletions src/Http/ResponseDecoderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Lmc\Matej\Http;

use Lmc\Matej\Model\Response;
use Psr\Http\Message\ResponseInterface;

interface ResponseDecoderInterface
{
public function decode(ResponseInterface $httpResponse): Response;
}
55 changes: 55 additions & 0 deletions src/Model/CommandResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

use Lmc\Matej\Exception\InvalidDomainModelArgumentException;

/**
* Response to one single command which was part of request batch.
*/
class CommandResponse
{
const STATUS_OK = 'OK';
const STATUS_ERROR = 'ERROR';
const STATUS_SKIPPED = 'SKIPPED';

/** @var string */
private $status;
/** @var string */
private $message;
/** @var array */
private $data = [];

private function __construct()
{
}

public static function createFromRawCommandResponseObject(\stdClass $rawCommandResponseObject): self
{
if (!isset($rawCommandResponseObject->status)) {
throw new InvalidDomainModelArgumentException('Status field is missing in command response object');
}

$commandResponse = new self();
$commandResponse->status = $rawCommandResponseObject->status;
$commandResponse->message = $rawCommandResponseObject->message ?? '';
$commandResponse->data = $rawCommandResponseObject->data ?? [];

return $commandResponse;
}

public function getStatus(): string
{
return $this->status;
}

public function getMessage(): string
{
return $this->message;
}

public function getData(): array
{
return $this->data;
}
}
73 changes: 73 additions & 0 deletions src/Model/Response.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

use Lmc\Matej\Exception\InvalidDomainModelArgumentException;

class Response
{
/** @var CommandResponse[] */
private $commandResponses = [];
/** @var int */
private $numberOfCommands;
/** @var int */
private $numberOfSuccessfulCommands;
/** @var int */
private $numberOfFailedCommands;

public function __construct(
int $numberOfCommands,
int $numberOfSuccessfulCommands,
int $numberOfFailedCommands,
array $commandResponses = []
) {
$this->numberOfCommands = $numberOfCommands;
$this->numberOfSuccessfulCommands = $numberOfSuccessfulCommands;
$this->numberOfFailedCommands = $numberOfFailedCommands;

foreach ($commandResponses as $rawCommandResponse) {
$this->commandResponses[] = CommandResponse::createFromRawCommandResponseObject($rawCommandResponse);
}

if ($this->numberOfCommands !== count($commandResponses)) {
throw InvalidDomainModelArgumentException::forInconsistentNumberOfCommands(
$this->numberOfCommands,
count($commandResponses)
);
}

if ($this->numberOfCommands !== ($this->numberOfSuccessfulCommands + $this->numberOfFailedCommands)) {
throw InvalidDomainModelArgumentException::forInconsistentNumbersOfCommandProperties(
$this->numberOfCommands,
$this->numberOfSuccessfulCommands,
$this->numberOfFailedCommands
);
}
}

public function getNumberOfCommands(): int
{
return $this->numberOfCommands;
}

public function getNumberOfSuccessfulCommands(): int
{
return $this->numberOfSuccessfulCommands;
}

public function getNumberOfFailedCommands(): int
{
return $this->numberOfFailedCommands;
}

/**
* 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.
*
* @return CommandResponse[]
*/
public function getCommandResponses(): array
{
return $this->commandResponses;
}
}
2 changes: 1 addition & 1 deletion tests/Exception/AuthorizationExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function shouldCreateExceptionFromJsonResponse(): void
'{"message": "Invalid signature. Check your secret key","result": "ERROR"}'
);

$exception = AuthorizationException::createFromRequestAndResponse($request, $response);
$exception = AuthorizationException::fromRequestAndResponse($request, $response);

$this->assertInstanceOf(AuthorizationException::class, $exception);
$this->assertSame(
Expand Down
7 changes: 7 additions & 0 deletions tests/Http/Fixtures/invalid-json.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /foo was not found on this server.</p>
</body></html>
26 changes: 26 additions & 0 deletions tests/Http/Fixtures/response-item-properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"commands": {
"number_of_commands": 3,
"number_of_failed_commands": 1,
"number_of_successful_commands": 2,
"responses": [
{
"data": [],
"message": "",
"status": "OK"
},
{
"data": [],
"message": "DuplicateKeyError(u'E11000 duplicate key error collection: foo.data_items_properties index: property_1 dup key: { : \"title\" }',)",
"status": "ERROR"
},
{
"data": [],
"message": "",
"status": "OK"
}
]
},
"message": "",
"status": "OK"
}
16 changes: 16 additions & 0 deletions tests/Http/Fixtures/response-one-successful-command.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"commands": {
"number_of_commands": 1,
"number_of_failed_commands": 0,
"number_of_successful_commands": 1,
"responses": [
{
"data": [],
"message": "",
"status": "OK"
}
]
},
"message": "",
"status": "OK"
}
80 changes: 80 additions & 0 deletions tests/Http/ResponseDecoderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);

namespace Lmc\Matej\Http;

use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\Psr7\Response;
use Lmc\Matej\Exception\ResponseDecodingException;
use Lmc\Matej\Model\CommandResponse;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;

class ResponseDecoderTest extends TestCase
{
/** @var ResponseDecoder */
protected $decoder;

/** @before */
public function init(): void
{
$this->decoder = new ResponseDecoder();
}

/** @test */
public function shouldDecodeSimpleOkResponse(): void
{
$response = $this->createJsonResponseFromFile(__DIR__ . '/Fixtures/response-one-successful-command.json');

$output = $this->decoder->decode($response);

$this->assertSame(1, $output->getNumberOfCommands());
$this->assertSame(1, $output->getNumberOfSuccessfulCommands());
$this->assertSame(0, $output->getNumberOfFailedCommands());

$commandResponses = $output->getCommandResponses();
$this->assertCount(1, $commandResponses);
$this->assertInstanceOf(CommandResponse::class, $commandResponses[0]);
$this->assertSame(CommandResponse::STATUS_OK, $commandResponses[0]->getStatus());
}

/** @test */
public function shouldDecodeResponseMultipleResponses(): void
{
$response = $this->createJsonResponseFromFile(__DIR__ . '/Fixtures/response-item-properties.json');

$output = $this->decoder->decode($response);

$this->assertSame(3, $output->getNumberOfCommands());
$this->assertSame(2, $output->getNumberOfSuccessfulCommands());
$this->assertSame(1, $output->getNumberOfFailedCommands());

$commandResponses = $output->getCommandResponses();
$this->assertCount(3, $commandResponses);
$this->assertContainsOnlyInstancesOf(CommandResponse::class, $commandResponses);
$this->assertSame(CommandResponse::STATUS_OK, $commandResponses[0]->getStatus());
$this->assertSame(CommandResponse::STATUS_ERROR, $commandResponses[1]->getStatus());
$this->assertSame(CommandResponse::STATUS_OK, $commandResponses[2]->getStatus());
}

/** @test */
public function shouldThrowExceptionWhenDecodingFails(): void
{
$notJsonData = file_get_contents(__DIR__ . '/Fixtures/invalid-json.html');
$response = new Response(StatusCodeInterface::STATUS_NOT_FOUND, [], $notJsonData);

$this->expectException(ResponseDecodingException::class);
$this->expectExceptionMessage('Error decoding Matej response');
$this->expectExceptionMessage('Status code: 404 Not Found');
$this->expectExceptionMessage('<p>The requested URL /foo was not found on this server.</p>');
$this->decoder->decode($response);
}

private function createJsonResponseFromFile(string $fileName): ResponseInterface
{
$jsonData = file_get_contents($fileName);
$response = new Response(StatusCodeInterface::STATUS_OK, ['Content-Type' => 'application/json'], $jsonData);

return $response;
}
}
Loading

0 comments on commit f98d085

Please sign in to comment.