From 9e86c522896bd9231a336545f7da4bcb051bebb5 Mon Sep 17 00:00:00 2001 From: prolic Date: Fri, 7 Jul 2017 21:38:41 +0800 Subject: [PATCH 01/17] initial implementation --- README.md | 8 +- composer.json | 6 +- src/HttplugEventStore.php | 265 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a9f4f9b..ce48d01 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,14 @@ The httplug event store is an implementation that uses httplug to communicate wi This example uses Guzzle6 httplug adapter ```php +$messageFactory = new \Prooph\Common\Messaging\FQCNMessageFactory(); +$messageConverter = new \Prooph\Common\Messaging\NoOpMessageConverter(); $httplug = new \Http\Adapter\Guzzle6\Client(); -$eventStore = new \Prooph\EventStore\Httplug($httpPlug, $options); +$uri = new \GuzzleHttp\Psr7\Uri('http:/localhost:8080'); -$streamEvents =$eventStore->load(new StreamName('test-stream')); +$eventStore = new \Prooph\EventStore\Httplug\HttplugEventStore($messageFactory, $messageConverter, $httpPlug, $uri); + +$streamEvents = $eventStore->load(new StreamName('test-stream')); ``` ## Support diff --git a/composer.json b/composer.json index 07a7e8a..125b980 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,11 @@ ], "require": { "php": "^7.1", - "prooph/event-store" : "^7.2" + "prooph/event-store": "^7.2", + "php-http/httplug": "^1.1.0", + "psr/http-message": "^1.0.1", + "php-http/message-factory": "^1.0.2", + "php-http/discovery": "^1.1.1" }, "require-dev": { "phpunit/phpunit": "^6.0", diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index b3d9bbc..5c0d47e 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -1 +1,266 @@ + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventStore\Httplug; + +use Http\Client\HttpClient; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Message\RequestFactory; +use Iterator; +use Prooph\Common\Messaging\MessageConverter; +use Prooph\Common\Messaging\MessageFactory; +use Prooph\EventStore\EventStore; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Exception\RuntimeException; +use Prooph\EventStore\Exception\StreamNotFound; +use Prooph\EventStore\Metadata\MetadataMatcher; +use Prooph\EventStore\Stream; +use Prooph\EventStore\StreamName; +use Psr\Http\Message\UriInterface; + +final class HttplugEventStore implements EventStore +{ + /** + * @var MessageFactory + */ + private $messageFactory; + + /** + * @var MessageConverter + */ + private $messageConverter; + + /** + * @var HttpClient + */ + private $httpClient; + + /** + * @var UriInterface + */ + private $uri; + + /** + * @var RequestFactory + */ + private $requestFactory; + + public function __construct( + MessageFactory $messageFactory, + MessageConverter $messageConverter, + HttpClient $httpClient, + UriInterface $uri, + RequestFactory $requestFactory = null + ) { + $this->messageFactory = $messageFactory; + $this->messageConverter = $messageConverter; + $this->httpClient = $httpClient; + $this->uri = $uri; + $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); + } + + public function updateStreamMetadata(StreamName $streamName, array $newMetadata): void + { + $body = json_encode($newMetadata); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidArgumentException('Metadata could not be json encoded'); + } + + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/streammetadata/' . urlencode($streamName->toString())), + [ + 'Content-Type' => 'application/json', + ], + $body + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 204: + break; + case 404: + throw StreamNotFound::with($streamName); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function create(Stream $stream): void + { + $messages = []; + + foreach ($stream->streamEvents() as $event) { + $message = $this->messageConverter->convertToArray($event); + $message['created_at'] = $message['created_at']->format('Y-m-d\TH:i:s.u'); + + $messages[] = $message; + } + + $body = json_encode($messages); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidArgumentException('Events could not be json encoded'); + } + + $streamName = $stream->streamName(); + + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/stream/' . urlencode($streamName->toString())), + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + $body + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 204: + if (! empty($stream->metadata())) { + $this->updateStreamMetadata($streamName, $stream->metadata()); + } + break; + case 400: + case 500: + throw new RuntimeException($response->getReasonPhrase()); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function appendTo(StreamName $streamName, Iterator $streamEvents): void + { + $stream = new Stream($streamName, $streamEvents); + + $this->create($stream); + } + + public function delete(StreamName $streamName): void + { + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/delete/' . urlencode($streamName->toString())), + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 204: + break; + case 404: + throw StreamNotFound::with($streamName); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function fetchStreamMetadata(StreamName $streamName): array + { + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath('/streammetadata/' . urlencode($streamName->toString())), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 200: + $metadata = json_decode($response->getBody()->getContents()); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $metadata; + case 404: + throw StreamNotFound::with($streamName); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function hasStream(StreamName $streamName): bool + { + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath('/has-stream/' . urlencode($streamName->toString())), + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 200: + break; + case 404: + throw StreamNotFound::with($streamName); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function load( + StreamName $streamName, + int $fromNumber = 1, + int $count = null, + MetadataMatcher $metadataMatcher = null + ): Iterator + { + // TODO: Implement load() method. + } + + public function loadReverse( + StreamName $streamName, + int $fromNumber = null, + int $count = null, + MetadataMatcher $metadataMatcher = null + ): Iterator + { + // TODO: Implement loadReverse() method. + } + + public function fetchStreamNames( + ?string $filter, + ?MetadataMatcher $metadataMatcher, + int $limit = 20, + int $offset = 0 + ): array + { + // TODO: Implement fetchStreamNames() method. + } + + public function fetchStreamNamesRegex( + string $filter, + ?MetadataMatcher $metadataMatcher, + int $limit = 20, + int $offset = 0 + ): array + { + // TODO: Implement fetchStreamNamesRegex() method. + } + + public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset = 0): array + { + // TODO: Implement fetchCategoryNames() method. + } + + public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $offset = 0): array + { + // TODO: Implement fetchCategoryNamesRegex() method. + } +} From 5042095255c45fb40633c725dd296ec482f07f64 Mon Sep 17 00:00:00 2001 From: prolic Date: Fri, 7 Jul 2017 21:48:49 +0800 Subject: [PATCH 02/17] apply php cs fixes --- src/HttplugEventStore.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 5c0d47e..cfd21ed 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -152,7 +152,7 @@ public function delete(StreamName $streamName): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/delete/' . urlencode($streamName->toString())), + $this->uri->withPath('/delete/' . urlencode($streamName->toString())) ); $response = $this->httpClient->sendRequest($request); @@ -199,7 +199,7 @@ public function hasStream(StreamName $streamName): bool { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/has-stream/' . urlencode($streamName->toString())), + $this->uri->withPath('/has-stream/' . urlencode($streamName->toString())) ); $response = $this->httpClient->sendRequest($request); @@ -219,8 +219,7 @@ public function load( int $fromNumber = 1, int $count = null, MetadataMatcher $metadataMatcher = null - ): Iterator - { + ): Iterator { // TODO: Implement load() method. } @@ -229,8 +228,7 @@ public function loadReverse( int $fromNumber = null, int $count = null, MetadataMatcher $metadataMatcher = null - ): Iterator - { + ): Iterator { // TODO: Implement loadReverse() method. } @@ -239,8 +237,7 @@ public function fetchStreamNames( ?MetadataMatcher $metadataMatcher, int $limit = 20, int $offset = 0 - ): array - { + ): array { // TODO: Implement fetchStreamNames() method. } @@ -249,8 +246,7 @@ public function fetchStreamNamesRegex( ?MetadataMatcher $metadataMatcher, int $limit = 20, int $offset = 0 - ): array - { + ): array { // TODO: Implement fetchStreamNamesRegex() method. } From 94b0debd84db0937098344fd2b721e67fa288484 Mon Sep 17 00:00:00 2001 From: prolic Date: Wed, 12 Jul 2017 18:27:35 +0800 Subject: [PATCH 03/17] finish implementation of httplug event store --- src/HttplugEventStore.php | 236 +++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 6 deletions(-) diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index cfd21ed..6cb18e0 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -12,6 +12,8 @@ namespace Prooph\EventStore\Httplug; +use DateTimeImmutable; +use DateTimeZone; use Http\Client\HttpClient; use Http\Discovery\MessageFactoryDiscovery; use Http\Message\RequestFactory; @@ -22,9 +24,11 @@ use Prooph\EventStore\Exception\InvalidArgumentException; use Prooph\EventStore\Exception\RuntimeException; use Prooph\EventStore\Exception\StreamNotFound; +use Prooph\EventStore\Metadata\FieldType; use Prooph\EventStore\Metadata\MetadataMatcher; use Prooph\EventStore\Stream; use Prooph\EventStore\StreamName; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; final class HttplugEventStore implements EventStore @@ -220,7 +224,34 @@ public function load( int $count = null, MetadataMatcher $metadataMatcher = null ): Iterator { - // TODO: Implement load() method. + if (null === $count) { + $count = PHP_INT_MAX; + } + + $uri = $this->uri + ->withPath('/stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/forward/' . $count) + ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); + + $request = $this->requestFactory->createRequest( + 'GET', + $uri, + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw StreamNotFound::with($streamName); + case 400: + throw new InvalidArgumentException($response->getReasonPhrase()); + case 200: + return $this->createIteratorFromResponse($response); + default: + throw new RuntimeException('Unknown error occurred'); + } } public function loadReverse( @@ -229,7 +260,38 @@ public function loadReverse( int $count = null, MetadataMatcher $metadataMatcher = null ): Iterator { - // TODO: Implement loadReverse() method. + if (null === $fromNumber) { + $fromNumber = PHP_INT_MAX; + } + + if (null === $count) { + $count = PHP_INT_MAX; + } + + $uri = $this->uri + ->withPath('/stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/backward/' . $count) + ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); + + $request = $this->requestFactory->createRequest( + 'GET', + $uri, + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw StreamNotFound::with($streamName); + case 400: + throw new InvalidArgumentException($response->getReasonPhrase()); + case 200: + return $this->createIteratorFromResponse($response); + default: + throw new RuntimeException('Unknown error occurred'); + } } public function fetchStreamNames( @@ -238,7 +300,43 @@ public function fetchStreamNames( int $limit = 20, int $offset = 0 ): array { - // TODO: Implement fetchStreamNames() method. + $limitPart = 'limit=' . $limit . '&offset=' . $offset; + + $query = $this->buildQueryFromMetadataMatcher($metadataMatcher); + + if ($query === '') { + $query = $limitPart; + } else { + $query .= '&' . $limitPart; + } + + if (null !== $filter) { + $path = '/streams/' . urlencode($filter); + } else { + $path = '/stream'; + } + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $streamNames = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $streamNames; } public function fetchStreamNamesRegex( @@ -247,16 +345,142 @@ public function fetchStreamNamesRegex( int $limit = 20, int $offset = 0 ): array { - // TODO: Implement fetchStreamNamesRegex() method. + $limitPart = 'limit=' . $limit . '&offset=' . $offset; + + $query = $this->buildQueryFromMetadataMatcher($metadataMatcher); + + if ($query === '') { + $query = $limitPart; + } else { + $query .= '&' . $limitPart; + } + + $path = '/streams-regex/' . urlencode($filter); + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $streamNames = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $streamNames; } public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset = 0): array { - // TODO: Implement fetchCategoryNames() method. + $query = 'limit=' . $limit . '&offset=' . $offset; + + if (null !== $filter) { + $path = '/categories/' . urlencode($filter); + } else { + $path = '/categories'; + } + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $categories = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $categories; } public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $offset = 0): array { - // TODO: Implement fetchCategoryNamesRegex() method. + $query = 'limit=' . $limit . '&offset=' . $offset; + + $path = '/categories-regex/' . urlencode($filter); + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $categories = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $categories; + } + + private function buildQueryFromMetadataMatcher(MetadataMatcher $metadataMatcher): string + { + $params = []; + + foreach ($metadataMatcher->data() as $key => $match) { + if (FieldType::METADATA()->is($match['fieldType'])) { + $prefix = 'meta_' . $key . '_'; + } else { + $prefix = 'property_' . $key . '_'; + } + + $params[] = $prefix . 'field=' . $match['field']; + $params[] = $prefix . 'operator=' . $match['operator']->getName(); + $params[] = $prefix . 'value=' . $match['value']; + } + + return implode('&', $params); + } + + private function createIteratorFromResponse(ResponseInterface $response): Iterator + { + $data = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response from event store'); + } + + foreach ($data['entries'] as $entry) { + $entry['created_at'] = DateTimeImmutable::createFromFormat( + 'Y-m-d\TH:i:s.u', + $entry['created_at'], + new DateTimeZone('UTC') + ); + + if (! $entry['created_at'] instanceof DateTimeImmutable) { + throw new RuntimeException('Could not create DateTimeImmutable object from event data'); + } + + yield $this->messageFactory->createMessageFromArray($entry['message_name'], $entry); + } } } From 0d11fc94019cb5ca59924aec0efa6cdb5244ffcd Mon Sep 17 00:00:00 2001 From: prolic Date: Wed, 12 Jul 2017 18:38:53 +0800 Subject: [PATCH 04/17] implement projection manager --- src/Projection/HttplugProjectionManager.php | 279 ++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/Projection/HttplugProjectionManager.php diff --git a/src/Projection/HttplugProjectionManager.php b/src/Projection/HttplugProjectionManager.php new file mode 100644 index 0000000..256b321 --- /dev/null +++ b/src/Projection/HttplugProjectionManager.php @@ -0,0 +1,279 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventStore\Httplug\Projection; + +use Http\Client\HttpClient; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Message\RequestFactory; +use Prooph\EventStore\Exception\ProjectionNotFound; +use Prooph\EventStore\Exception\RuntimeException; +use Prooph\EventStore\Projection\ProjectionManager; +use Prooph\EventStore\Projection\ProjectionStatus; +use Prooph\EventStore\Projection\Projector; +use Prooph\EventStore\Projection\Query; +use Prooph\EventStore\Projection\ReadModel; +use Prooph\EventStore\Projection\ReadModelProjector; +use Psr\Http\Message\UriInterface; + +final class HttplugProjectionManager implements ProjectionManager +{ + /** + * @var HttpClient + */ + private $httpClient; + + /** + * @var UriInterface + */ + private $uri; + + /** + * @var RequestFactory + */ + private $requestFactory; + + public function __construct( + HttpClient $httpClient, + UriInterface $uri, + RequestFactory $requestFactory = null + ) { + $this->httpClient = $httpClient; + $this->uri = $uri; + $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); + } + + public function createQuery(): Query + { + throw new \BadMethodCallException(__METHOD__ . ' not implemented'); + } + + public function createProjection( + string $name, + array $options = [] + ): Projector + { + throw new \BadMethodCallException(__METHOD__ . ' not implemented'); + } + + public function createReadModelProjection( + string $name, + ReadModel $readModel, + array $options = [] + ): ReadModelProjector + { + throw new \BadMethodCallException(__METHOD__ . ' not implemented'); + } + + public function deleteProjection(string $name, bool $deleteEmittedEvents): void + { + if ($deleteEmittedEvents) { + $deleteEmittedEvents = 'true'; + } else { + $deleteEmittedEvents = 'false'; + } + + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/projection/delete/' . urlencode($name) . '/' . $deleteEmittedEvents) + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 204: + break; + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function resetProjection(string $name): void + { + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/projection/reset/' . urlencode($name)) + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 204: + break; + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function stopProjection(string $name): void + { + $request = $this->requestFactory->createRequest( + 'POST', + $this->uri->withPath('/projection/stop/' . urlencode($name)) + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 204: + break; + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function fetchProjectionNames(?string $filter, int $limit = 20, int $offset = 0): array + { + $query = 'limit=' . $limit . '&offset=' . $offset; + + if (null !== $filter) { + $path = '/projections/' . urlencode($filter); + } else { + $path = '/projections'; + } + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $projectionNames = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $projectionNames; + } + + public function fetchProjectionNamesRegex(string $regex, int $limit = 20, int $offset = 0): array + { + $query = 'limit=' . $limit . '&offset=' . $offset; + + $path = '/projections-regex/' . urlencode($regex); + + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath($path)->withQuery($query), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + throw new RuntimeException('Unknown error occurred'); + } + + $projectionNames = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $projectionNames; + } + + public function fetchProjectionStatus(string $name): ProjectionStatus + { + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath('/projection/status/' . urlencode($name)), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 200: + return ProjectionStatus::byName($response->getReasonPhrase()); + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function fetchProjectionStreamPositions(string $name): array + { + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath('/projection/stream-positions/' . urlencode($name)), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 200: + $streamPositions = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $streamPositions; + default: + throw new RuntimeException('Unknown error occurred'); + } + } + + public function fetchProjectionState(string $name): array + { + $request = $this->requestFactory->createRequest( + 'GET', + $this->uri->withPath('/projection/state/' . urlencode($name)), + [ + 'Accept' => 'application/json', + ] + ); + + $response = $this->httpClient->sendRequest($request); + + switch ($response->getStatusCode()) { + case 404: + throw ProjectionNotFound::withName($name); + case 200: + $state = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } + + return $state; + default: + throw new RuntimeException('Unknown error occurred'); + } + } +} From 86e339af55284c966077aa31a213d0510c3c2302 Mon Sep 17 00:00:00 2001 From: prolic Date: Wed, 12 Jul 2017 18:55:19 +0800 Subject: [PATCH 05/17] fix relative es paths, add container factories --- src/Container/HttplugEventStoreFactory.php | 115 ++++++++++++++++++ .../HttplugProjectionManagerFactory.php | 101 +++++++++++++++ src/HttplugEventStore.php | 26 ++-- src/Projection/HttplugProjectionManager.php | 20 ++- 4 files changed, 238 insertions(+), 24 deletions(-) create mode 100644 src/Container/HttplugEventStoreFactory.php create mode 100644 src/Container/Projection/HttplugProjectionManagerFactory.php diff --git a/src/Container/HttplugEventStoreFactory.php b/src/Container/HttplugEventStoreFactory.php new file mode 100644 index 0000000..b3157ed --- /dev/null +++ b/src/Container/HttplugEventStoreFactory.php @@ -0,0 +1,115 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventStore\Httplug\Container; + +use Http\Discovery\UriFactoryDiscovery; +use Interop\Config\ConfigurationTrait; +use Interop\Config\ProvidesDefaultOptions; +use Interop\Config\RequiresConfigId; +use Interop\Config\RequiresMandatoryOptions; +use Prooph\Common\Messaging\FQCNMessageFactory; +use Prooph\Common\Messaging\NoOpMessageConverter; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Httplug\HttplugEventStore; +use Psr\Container\ContainerInterface; + +final class HttplugEventStoreFactory implements + ProvidesDefaultOptions, + RequiresConfigId, + RequiresMandatoryOptions +{ + use ConfigurationTrait; + + /** + * @var string + */ + private $configId; + + /** + * Creates a new instance from a specified config, specifically meant to be used as static factory. + * + * In case you want to use another config key than provided by the factories, you can add the following factory to + * your config: + * + * + * [HttplugEventStoreFactory::class, 'service_name'], + * ]; + * + * + * @throws InvalidArgumentException + */ + public static function __callStatic(string $name, array $arguments): HttplugEventStore + { + if (! isset($arguments[0]) || ! $arguments[0] instanceof ContainerInterface) { + throw new InvalidArgumentException( + sprintf('The first argument must be of type %s', ContainerInterface::class) + ); + } + + return (new static($name))->__invoke($arguments[0]); + } + + public function __construct(string $configId = 'default') + { + $this->configId = $configId; + } + + public function __invoke(ContainerInterface $container): HttplugEventStore + { + $config = $container->get('config'); + $config = $this->options($config, $this->configId); + + if (isset($config['uri_factory'])) { + $uriFactory = $container->get($config['uri_factory']); + } else { + $uriFactory = UriFactoryDiscovery::find(); + } + + $requestFactory = null; + + if (isset($config['request_factory'])) { + $requestFactory = $container->get($config['request_factory']); + } + + return new HttplugEventStore( + $container->get($config['message_factory']), + $container->get($config['message_converter']), + $container->get($config['http_client']), + $uriFactory->createUri($config['uri']), + $requestFactory + ); + } + + public function dimensions(): iterable + { + return ['prooph', 'event_store']; + } + + public function defaultOptions(): iterable + { + return [ + 'message_factory' => FQCNMessageFactory::class, + 'message_converter' => NoOpMessageConverter::class, + ]; + } + + public function mandatoryOptions(): iterable + { + return [ + 'http_client', + 'uri', + ]; + } +} diff --git a/src/Container/Projection/HttplugProjectionManagerFactory.php b/src/Container/Projection/HttplugProjectionManagerFactory.php new file mode 100644 index 0000000..efa4b78 --- /dev/null +++ b/src/Container/Projection/HttplugProjectionManagerFactory.php @@ -0,0 +1,101 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventStore\Httplug\Container\Projection; + +use Http\Discovery\UriFactoryDiscovery; +use Interop\Config\ConfigurationTrait; +use Interop\Config\RequiresConfigId; +use Interop\Config\RequiresMandatoryOptions; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Httplug\Projection\HttplugProjectionManager; +use Psr\Container\ContainerInterface; + +final class HttplugProjectionManagerFactory implements + RequiresConfigId, + RequiresMandatoryOptions +{ + use ConfigurationTrait; + + /** + * @var string + */ + private $configId; + + /** + * Creates a new instance from a specified config, specifically meant to be used as static factory. + * + * In case you want to use another config key than provided by the factories, you can add the following factory to + * your config: + * + * + * [HttplugProjectionManagerFactory::class, 'service_name'], + * ]; + * + * + * @throws InvalidArgumentException + */ + public static function __callStatic(string $name, array $arguments): HttplugProjectionManager + { + if (! isset($arguments[0]) || ! $arguments[0] instanceof ContainerInterface) { + throw new InvalidArgumentException( + sprintf('The first argument must be of type %s', ContainerInterface::class) + ); + } + + return (new static($name))->__invoke($arguments[0]); + } + + public function __construct(string $configId = 'default') + { + $this->configId = $configId; + } + + public function __invoke(ContainerInterface $container): HttplugProjectionManager + { + $config = $container->get('config'); + $config = $this->options($config, $this->configId); + + if (isset($config['uri_factory'])) { + $uriFactory = $container->get($config['uri_factory']); + } else { + $uriFactory = UriFactoryDiscovery::find(); + } + + $requestFactory = null; + + if (isset($config['request_factory'])) { + $requestFactory = $container->get($config['request_factory']); + } + + return new HttplugProjectionManager( + $container->get($config['http_client']), + $uriFactory->createUri($config['uri']), + $requestFactory + ); + } + + public function dimensions(): iterable + { + return ['prooph', 'projection_manager']; + } + + public function mandatoryOptions(): iterable + { + return [ + 'http_client', + 'uri', + ]; + } +} diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 6cb18e0..494cec7 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -82,7 +82,7 @@ public function updateStreamMetadata(StreamName $streamName, array $newMetadata) $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/streammetadata/' . urlencode($streamName->toString())), + $this->uri->withPath('streammetadata/' . urlencode($streamName->toString())), [ 'Content-Type' => 'application/json', ], @@ -122,7 +122,7 @@ public function create(Stream $stream): void $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/stream/' . urlencode($streamName->toString())), + $this->uri->withPath('stream/' . urlencode($streamName->toString())), [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -156,7 +156,7 @@ public function delete(StreamName $streamName): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/delete/' . urlencode($streamName->toString())) + $this->uri->withPath('delete/' . urlencode($streamName->toString())) ); $response = $this->httpClient->sendRequest($request); @@ -175,7 +175,7 @@ public function fetchStreamMetadata(StreamName $streamName): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/streammetadata/' . urlencode($streamName->toString())), + $this->uri->withPath('streammetadata/' . urlencode($streamName->toString())), [ 'Accept' => 'application/json', ] @@ -203,7 +203,7 @@ public function hasStream(StreamName $streamName): bool { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/has-stream/' . urlencode($streamName->toString())) + $this->uri->withPath('has-stream/' . urlencode($streamName->toString())) ); $response = $this->httpClient->sendRequest($request); @@ -229,7 +229,7 @@ public function load( } $uri = $this->uri - ->withPath('/stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/forward/' . $count) + ->withPath('stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/forward/' . $count) ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); $request = $this->requestFactory->createRequest( @@ -269,7 +269,7 @@ public function loadReverse( } $uri = $this->uri - ->withPath('/stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/backward/' . $count) + ->withPath('stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/backward/' . $count) ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); $request = $this->requestFactory->createRequest( @@ -311,9 +311,9 @@ public function fetchStreamNames( } if (null !== $filter) { - $path = '/streams/' . urlencode($filter); + $path = 'streams/' . urlencode($filter); } else { - $path = '/stream'; + $path = 'stream'; } $request = $this->requestFactory->createRequest( @@ -355,7 +355,7 @@ public function fetchStreamNamesRegex( $query .= '&' . $limitPart; } - $path = '/streams-regex/' . urlencode($filter); + $path = 'streams-regex/' . urlencode($filter); $request = $this->requestFactory->createRequest( 'GET', @@ -385,9 +385,9 @@ public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset $query = 'limit=' . $limit . '&offset=' . $offset; if (null !== $filter) { - $path = '/categories/' . urlencode($filter); + $path = 'categories/' . urlencode($filter); } else { - $path = '/categories'; + $path = 'categories'; } $request = $this->requestFactory->createRequest( @@ -417,7 +417,7 @@ public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $of { $query = 'limit=' . $limit . '&offset=' . $offset; - $path = '/categories-regex/' . urlencode($filter); + $path = 'categories-regex/' . urlencode($filter); $request = $this->requestFactory->createRequest( 'GET', diff --git a/src/Projection/HttplugProjectionManager.php b/src/Projection/HttplugProjectionManager.php index 256b321..c62323a 100644 --- a/src/Projection/HttplugProjectionManager.php +++ b/src/Projection/HttplugProjectionManager.php @@ -60,8 +60,7 @@ public function createQuery(): Query public function createProjection( string $name, array $options = [] - ): Projector - { + ): Projector { throw new \BadMethodCallException(__METHOD__ . ' not implemented'); } @@ -69,8 +68,7 @@ public function createReadModelProjection( string $name, ReadModel $readModel, array $options = [] - ): ReadModelProjector - { + ): ReadModelProjector { throw new \BadMethodCallException(__METHOD__ . ' not implemented'); } @@ -84,7 +82,7 @@ public function deleteProjection(string $name, bool $deleteEmittedEvents): void $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/projection/delete/' . urlencode($name) . '/' . $deleteEmittedEvents) + $this->uri->withPath('projection/delete/' . urlencode($name) . '/' . $deleteEmittedEvents) ); $response = $this->httpClient->sendRequest($request); @@ -103,7 +101,7 @@ public function resetProjection(string $name): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/projection/reset/' . urlencode($name)) + $this->uri->withPath('projection/reset/' . urlencode($name)) ); $response = $this->httpClient->sendRequest($request); @@ -122,7 +120,7 @@ public function stopProjection(string $name): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('/projection/stop/' . urlencode($name)) + $this->uri->withPath('projection/stop/' . urlencode($name)) ); $response = $this->httpClient->sendRequest($request); @@ -174,7 +172,7 @@ public function fetchProjectionNamesRegex(string $regex, int $limit = 20, int $o { $query = 'limit=' . $limit . '&offset=' . $offset; - $path = '/projections-regex/' . urlencode($regex); + $path = 'projections-regex/' . urlencode($regex); $request = $this->requestFactory->createRequest( 'GET', @@ -203,7 +201,7 @@ public function fetchProjectionStatus(string $name): ProjectionStatus { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/projection/status/' . urlencode($name)), + $this->uri->withPath('projection/status/' . urlencode($name)), [ 'Accept' => 'application/json', ] @@ -225,7 +223,7 @@ public function fetchProjectionStreamPositions(string $name): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/projection/stream-positions/' . urlencode($name)), + $this->uri->withPath('projection/stream-positions/' . urlencode($name)), [ 'Accept' => 'application/json', ] @@ -253,7 +251,7 @@ public function fetchProjectionState(string $name): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('/projection/state/' . urlencode($name)), + $this->uri->withPath('projection/state/' . urlencode($name)), [ 'Accept' => 'application/json', ] From 5cea7e9f09b0a58746b76a8338e786b4672ccce8 Mon Sep 17 00:00:00 2001 From: prolic Date: Wed, 12 Jul 2017 19:19:14 +0800 Subject: [PATCH 06/17] add httplug event store tests --- composer.json | 8 +- tests/HttplugEventStoreTest.php | 136 ++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 tests/HttplugEventStoreTest.php diff --git a/composer.json b/composer.json index 125b980..555ae5c 100644 --- a/composer.json +++ b/composer.json @@ -52,13 +52,7 @@ }, "autoload-dev": { "psr-4": { - "ProophTest\\EventStore\\Httplug\\": "tests/", - "ProophTest\\EventStore\\": "vendor/prooph/event-store/tests/" - } - }, - "config": { - "preferred-install": { - "prooph/*": "source" + "ProophTest\\EventStore\\Httplug\\": "tests/" } }, "scripts": { diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php new file mode 100644 index 0000000..5a96d76 --- /dev/null +++ b/tests/HttplugEventStoreTest.php @@ -0,0 +1,136 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ProophTest\HttplugEventStore; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use PHPUnit\Framework\TestCase; +use Prooph\Common\Messaging\MessageConverter; +use Prooph\Common\Messaging\MessageFactory; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Exception\StreamNotFound; +use Prooph\EventStore\Httplug\HttplugEventStore; +use Prooph\EventStore\StreamName; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; + +class HttplugEventStoreTest extends TestCase +{ + /** + * @test + */ + public function it_throws_exception_when_cannot_json_encode_metadata_for_update(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Metadata could not be json encoded'); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $this->prophesize(HttpClient::class)->reveal(), + $this->prophesize(UriInterface::class)->reveal(), + $this->prophesize(RequestFactory::class)->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('test'), ["\xB1\x31"]); + } + + /** + * @test + */ + public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_metadata(): void + { + $this->expectException(StreamNotFound::class); + + $finalUrl = $this->prophesize(UriInterface::class); + $finalUrl = $finalUrl->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUrl, + [ + 'Content-Type' => 'application/json', + ], + json_encode(['some' => 'value']) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('unknown'), ['some' => 'value']); + } + + /** + * @test + */ + public function it_updates_stream_metadata(): void + { + $finalUrl = $this->prophesize(UriInterface::class); + $finalUrl = $finalUrl->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('streammetadata/somename')->willReturn($finalUrl); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUrl, + [ + 'Content-Type' => 'application/json', + ], + json_encode(['some' => 'value']) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('somename'), ['some' => 'value']); + } +} From b3c1900b3dd3e597e9d50d497bc6a23d8acddcdb Mon Sep 17 00:00:00 2001 From: prolic Date: Thu, 13 Jul 2017 22:07:17 +0800 Subject: [PATCH 07/17] add not allowed error handling, add tests --- composer.json | 8 +- src/Exception/NotAllowed.php | 20 + src/HttplugEventStore.php | 102 ++-- src/Projection/HttplugProjectionManager.php | 59 ++- tests/HttplugEventStoreTest.php | 504 +++++++++++++++++++- 5 files changed, 637 insertions(+), 56 deletions(-) create mode 100644 src/Exception/NotAllowed.php diff --git a/composer.json b/composer.json index 555ae5c..0e1c464 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,8 @@ }, "autoload": { "psr-4": { - "Prooph\\EventStore\\Httplug\\": "src/" + "Prooph\\EventStore\\Httplug\\": "src/", + "ProophTest\\EventStore\\": "vendor/prooph/event-store/tests/" } }, "autoload-dev": { @@ -55,6 +56,11 @@ "ProophTest\\EventStore\\Httplug\\": "tests/" } }, + "config": { + "preferred-install": { + "prooph/*": "source" + } + }, "scripts": { "check": [ "@cs", diff --git a/src/Exception/NotAllowed.php b/src/Exception/NotAllowed.php new file mode 100644 index 0000000..78963a4 --- /dev/null +++ b/src/Exception/NotAllowed.php @@ -0,0 +1,20 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventStore\Httplug\Exception; + +use Prooph\EventStore\Exception\RuntimeException; + +class NotAllowed extends RuntimeException +{ + protected $message = 'You are not allowed to access this resource'; +} diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 494cec7..8c2fad4 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -24,6 +24,7 @@ use Prooph\EventStore\Exception\InvalidArgumentException; use Prooph\EventStore\Exception\RuntimeException; use Prooph\EventStore\Exception\StreamNotFound; +use Prooph\EventStore\Httplug\Exception\NotAllowed; use Prooph\EventStore\Metadata\FieldType; use Prooph\EventStore\Metadata\MetadataMatcher; use Prooph\EventStore\Stream; @@ -96,6 +97,9 @@ public function updateStreamMetadata(StreamName $streamName, array $newMetadata) break; case 404: throw StreamNotFound::with($streamName); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -138,8 +142,10 @@ public function create(Stream $stream): void } break; case 400: - case 500: throw new RuntimeException($response->getReasonPhrase()); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -166,6 +172,9 @@ public function delete(StreamName $streamName): void break; case 404: throw StreamNotFound::with($streamName); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -194,6 +203,9 @@ public function fetchStreamMetadata(StreamName $streamName): array return $metadata; case 404: throw StreamNotFound::with($streamName); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -213,6 +225,9 @@ public function hasStream(StreamName $streamName): bool break; case 404: throw StreamNotFound::with($streamName); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -249,6 +264,9 @@ public function load( throw new InvalidArgumentException($response->getReasonPhrase()); case 200: return $this->createIteratorFromResponse($response); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -289,6 +307,9 @@ public function loadReverse( throw new InvalidArgumentException($response->getReasonPhrase()); case 200: return $this->createIteratorFromResponse($response); + case 403: + case 405: + throw new NotAllowed(); default: throw new RuntimeException('Unknown error occurred'); } @@ -326,17 +347,21 @@ public function fetchStreamNames( $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $streamNames = json_decode($response->getBody()->getContents(), true); - $streamNames = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $streamNames; + default: + throw new RuntimeException('Unknown error occurred'); } - - return $streamNames; } public function fetchStreamNamesRegex( @@ -367,17 +392,21 @@ public function fetchStreamNamesRegex( $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $streamNames = json_decode($response->getBody()->getContents(), true); - $streamNames = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $streamNames; + default: + throw new RuntimeException('Unknown error occurred'); } - - return $streamNames; } public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset = 0): array @@ -400,17 +429,21 @@ public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $categories = json_decode($response->getBody()->getContents(), true); - $categories = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $categories; + default: + throw new RuntimeException('Unknown error occurred'); } - - return $categories; } public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $offset = 0): array @@ -429,17 +462,22 @@ public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $of $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $categories = json_decode($response->getBody()->getContents(), true); - $categories = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $categories; + default: + throw new RuntimeException('Unknown error occurred'); } - return $categories; } private function buildQueryFromMetadataMatcher(MetadataMatcher $metadataMatcher): string diff --git a/src/Projection/HttplugProjectionManager.php b/src/Projection/HttplugProjectionManager.php index c62323a..45a7f4b 100644 --- a/src/Projection/HttplugProjectionManager.php +++ b/src/Projection/HttplugProjectionManager.php @@ -17,6 +17,7 @@ use Http\Message\RequestFactory; use Prooph\EventStore\Exception\ProjectionNotFound; use Prooph\EventStore\Exception\RuntimeException; +use Prooph\EventStore\Httplug\Exception\NotAllowed; use Prooph\EventStore\Projection\ProjectionManager; use Prooph\EventStore\Projection\ProjectionStatus; use Prooph\EventStore\Projection\Projector; @@ -88,6 +89,9 @@ public function deleteProjection(string $name, bool $deleteEmittedEvents): void $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 204: @@ -107,6 +111,9 @@ public function resetProjection(string $name): void $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 204: @@ -126,6 +133,9 @@ public function stopProjection(string $name): void $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 204: @@ -155,17 +165,21 @@ public function fetchProjectionNames(?string $filter, int $limit = 20, int $offs $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $projectionNames = json_decode($response->getBody()->getContents(), true); - $projectionNames = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $projectionNames; + default: + throw new RuntimeException('Unknown error occurred'); } - - return $projectionNames; } public function fetchProjectionNamesRegex(string $regex, int $limit = 20, int $offset = 0): array @@ -184,17 +198,21 @@ public function fetchProjectionNamesRegex(string $regex, int $limit = 20, int $o $response = $this->httpClient->sendRequest($request); - if ($response->getStatusCode() !== 200) { - throw new RuntimeException('Unknown error occurred'); - } + switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); + case 200: + $projectionNames = json_decode($response->getBody()->getContents(), true); - $projectionNames = json_decode($response->getBody()->getContents(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not json decode response'); + } - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not json decode response'); + return $projectionNames; + default: + throw new RuntimeException('Unknown error occurred'); } - - return $projectionNames; } public function fetchProjectionStatus(string $name): ProjectionStatus @@ -210,6 +228,9 @@ public function fetchProjectionStatus(string $name): ProjectionStatus $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 200: @@ -232,6 +253,9 @@ public function fetchProjectionStreamPositions(string $name): array $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 200: @@ -260,6 +284,9 @@ public function fetchProjectionState(string $name): array $response = $this->httpClient->sendRequest($request); switch ($response->getStatusCode()) { + case 403: + case 405: + throw new NotAllowed(); case 404: throw ProjectionNotFound::withName($name); case 200: diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 5a96d76..3ae6468 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -17,16 +17,156 @@ use PHPUnit\Framework\TestCase; use Prooph\Common\Messaging\MessageConverter; use Prooph\Common\Messaging\MessageFactory; +use Prooph\Common\Messaging\NoOpMessageConverter; use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Exception\RuntimeException; use Prooph\EventStore\Exception\StreamNotFound; +use Prooph\EventStore\Httplug\Exception\NotAllowed; use Prooph\EventStore\Httplug\HttplugEventStore; +use Prooph\EventStore\Stream; use Prooph\EventStore\StreamName; +use ProophTest\Common\Mock\SomethingWasDone; +use ProophTest\EventStore\Mock\TestDomainEvent; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; class HttplugEventStoreTest extends TestCase { + // + /** + * @test + */ + public function it_updates_stream_metadata(): void + { + $finalUrl = $this->prophesize(UriInterface::class); + $finalUrl = $finalUrl->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('streammetadata/somename')->willReturn($finalUrl); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUrl, + [ + 'Content-Type' => 'application/json', + ], + json_encode(['some' => 'value']) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('somename'), ['some' => 'value']); + } + + /** + * @test + */ + public function it_throws_exception_when_forbidden_to_update_metadata(): void + { + $this->expectException(NotAllowed::class); + + $finalUrl = $this->prophesize(UriInterface::class); + $finalUrl = $finalUrl->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUrl, + [ + 'Content-Type' => 'application/json', + ], + json_encode(['some' => 'value']) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('unknown'), ['some' => 'value']); + } + + /** + * @test + */ + public function it_throws_exception_on_unknown_error_when_updating_metadata(): void + { + $this->expectException(RuntimeException::class); + + $finalUrl = $this->prophesize(UriInterface::class); + $finalUrl = $finalUrl->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUrl, + [ + 'Content-Type' => 'application/json', + ], + json_encode(['some' => 'value']) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->updateStreamMetadata(new StreamName('unknown'), ['some' => 'value']); + } + /** * @test */ @@ -90,30 +230,256 @@ public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_ $eventStore->updateStreamMetadata(new StreamName('unknown'), ['some' => 'value']); } + // + // /** * @test */ - public function it_updates_stream_metadata(): void + public function it_creates_stream(): void { - $finalUrl = $this->prophesize(UriInterface::class); - $finalUrl = $finalUrl->reveal(); + $messageConverter = new NoOpMessageConverter(); + + $message = TestDomainEvent::with(['foo' => 'bar'], 1); + $messageData = $messageConverter->convertToArray($message); + $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $finalUri2 = $this->prophesize(UriInterface::class); + $finalUri2 = $finalUri2->reveal(); $uri = $this->prophesize(UriInterface::class); - $uri->withPath('streammetadata/somename')->willReturn($finalUrl); + $uri->withPath('stream/somename')->willReturn($finalUri); + $uri->withPath('streammetadata/somename')->willReturn($finalUri2); $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); + $request2 = $this->prophesize(RequestInterface::class); + $request2 = $request2->reveal(); + $requestFactory = $this->prophesize(RequestFactory::class); $requestFactory ->createRequest( 'POST', - $finalUrl, + $finalUri, + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + json_encode([$messageData]) + ) + ->willReturn($request); + + $requestFactory + ->createRequest( + 'POST', + $finalUri2, [ 'Content-Type' => 'application/json', ], - json_encode(['some' => 'value']) + json_encode(['some' => 'meta']) + ) + ->willReturn($request2); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $response2 = $this->prophesize(ResponseInterface::class); + $response2->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + $httpClient->sendRequest($request2)->willReturn($response2->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $messageConverter, + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->create(new Stream(new StreamName('somename'), new \ArrayIterator([$message]), ['some' => 'meta'])); + } + + /** + * @test + */ + public function it_creates_stream_and_throws_error_on_400(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('some message'); + + $messageConverter = new NoOpMessageConverter(); + + $message = TestDomainEvent::with(['foo' => 'bar'], 1); + $messageData = $messageConverter->convertToArray($message); + $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('stream/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri, + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + json_encode([$messageData]) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(400)->shouldBeCalled(); + $response->getReasonPhrase()->willReturn('some message')->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $messageConverter, + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->create(new Stream(new StreamName('somename'), new \ArrayIterator([$message]), ['some' => 'meta'])); + } + + /** + * @test + */ + public function it_creates_stream_and_throws_forbidden_on_403(): void + { + $this->expectException(NotAllowed::class); + + $messageConverter = new NoOpMessageConverter(); + + $message = TestDomainEvent::with(['foo' => 'bar'], 1); + $messageData = $messageConverter->convertToArray($message); + $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('stream/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri, + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + json_encode([$messageData]) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(403)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $messageConverter, + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->create(new Stream(new StreamName('somename'), new \ArrayIterator([$message]), ['some' => 'meta'])); + } + // + + // + /** + * @test + */ + public function it_appends_to_stream_and_creates_it_automatically(): void + { + $messageConverter = new NoOpMessageConverter(); + + $message = TestDomainEvent::with(['foo' => 'bar'], 1); + $messageData = $messageConverter->convertToArray($message); + $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('stream/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri, + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + json_encode([$messageData]) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $messageConverter, + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->appendTo(new StreamName('somename'), new \ArrayIterator([$message])); + } + // + + // + /** + * @test + */ + public function it_deletes_stream(): void + { + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('delete/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri ) ->willReturn($request); @@ -131,6 +497,130 @@ public function it_updates_stream_metadata(): void $requestFactory->reveal() ); - $eventStore->updateStreamMetadata(new StreamName('somename'), ['some' => 'value']); + $eventStore->delete(new StreamName('somename')); + } + + /** + * @test + */ + public function it_cannot_delete_stream_when_not_found(): void + { + $this->expectException(StreamNotFound::class); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('delete/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->delete(new StreamName('somename')); + } + + /** + * @test + */ + public function it_cannot_delete_stream_when_not_allowed(): void + { + $this->expectException(NotAllowed::class); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('delete/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->delete(new StreamName('somename')); + } + + /** + * @test + */ + public function it_handles_unknown_errors_on_delete(): void + { + $this->expectException(RuntimeException::class); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('delete/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->delete(new StreamName('somename')); } + // } From e65ae1ef21bcb265fe70246ce62a4ef02a215c8d Mon Sep 17 00:00:00 2001 From: prolic Date: Thu, 13 Jul 2017 22:08:05 +0800 Subject: [PATCH 08/17] apply php cs fixes --- src/HttplugEventStore.php | 1 - tests/HttplugEventStoreTest.php | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 8c2fad4..81ed336 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -477,7 +477,6 @@ public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $of default: throw new RuntimeException('Unknown error occurred'); } - } private function buildQueryFromMetadataMatcher(MetadataMatcher $metadataMatcher): string diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 3ae6468..8906a0a 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -25,7 +25,6 @@ use Prooph\EventStore\Httplug\HttplugEventStore; use Prooph\EventStore\Stream; use Prooph\EventStore\StreamName; -use ProophTest\Common\Mock\SomethingWasDone; use ProophTest\EventStore\Mock\TestDomainEvent; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -34,6 +33,7 @@ class HttplugEventStoreTest extends TestCase { // + /** * @test */ @@ -230,9 +230,11 @@ public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_ $eventStore->updateStreamMetadata(new StreamName('unknown'), ['some' => 'value']); } + // // + /** * @test */ @@ -407,9 +409,11 @@ public function it_creates_stream_and_throws_forbidden_on_403(): void $eventStore->create(new Stream(new StreamName('somename'), new \ArrayIterator([$message]), ['some' => 'meta'])); } + // // + /** * @test */ @@ -458,9 +462,11 @@ public function it_appends_to_stream_and_creates_it_automatically(): void $eventStore->appendTo(new StreamName('somename'), new \ArrayIterator([$message])); } + // // + /** * @test */ @@ -622,5 +628,6 @@ public function it_handles_unknown_errors_on_delete(): void $eventStore->delete(new StreamName('somename')); } + // } From e0a7fbadb0e1f4321fa49ed2a413dc585d290743 Mon Sep 17 00:00:00 2001 From: prolic Date: Thu, 13 Jul 2017 22:10:15 +0800 Subject: [PATCH 09/17] add test --- tests/HttplugEventStoreTest.php | 55 +++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 8906a0a..2a0f021 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -362,7 +362,7 @@ public function it_creates_stream_and_throws_error_on_400(): void /** * @test */ - public function it_creates_stream_and_throws_forbidden_on_403(): void + public function it_cannot_create_stream_when_not_allowed(): void { $this->expectException(NotAllowed::class); @@ -394,7 +394,58 @@ public function it_creates_stream_and_throws_forbidden_on_403(): void ->willReturn($request); $response = $this->prophesize(ResponseInterface::class); - $response->getStatusCode()->willReturn(403)->shouldBeCalled(); + $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $messageConverter, + $httpClient->reveal(), + $uri->reveal(), + $requestFactory->reveal() + ); + + $eventStore->create(new Stream(new StreamName('somename'), new \ArrayIterator([$message]), ['some' => 'meta'])); + } + + /** + * @test + */ + public function it_handles_unknown_errors_on_create(): void + { + $this->expectException(RuntimeException::class); + + $messageConverter = new NoOpMessageConverter(); + + $message = TestDomainEvent::with(['foo' => 'bar'], 1); + $messageData = $messageConverter->convertToArray($message); + $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); + + $finalUri = $this->prophesize(UriInterface::class); + $finalUri = $finalUri->reveal(); + + $uri = $this->prophesize(UriInterface::class); + $uri->withPath('stream/somename')->willReturn($finalUri); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'POST', + $finalUri, + [ + 'Content-Type' => 'application/vnd.eventstore.atom+json', + ], + json_encode([$messageData]) + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); $httpClient = $this->prophesize(HttpClient::class); $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); From bcb02bcd067098f6df8b674719a3e3c8b63cec85 Mon Sep 17 00:00:00 2001 From: prolic Date: Sat, 15 Jul 2017 17:38:45 +0800 Subject: [PATCH 10/17] remove uri interface from event store and pm --- src/Container/HttplugEventStoreFactory.php | 8 -- .../HttplugProjectionManagerFactory.php | 8 -- src/HttplugEventStore.php | 50 +++---- src/Projection/HttplugProjectionManager.php | 32 ++--- tests/HttplugEventStoreTest.php | 125 +++--------------- 5 files changed, 45 insertions(+), 178 deletions(-) diff --git a/src/Container/HttplugEventStoreFactory.php b/src/Container/HttplugEventStoreFactory.php index b3157ed..eb5eeaf 100644 --- a/src/Container/HttplugEventStoreFactory.php +++ b/src/Container/HttplugEventStoreFactory.php @@ -71,12 +71,6 @@ public function __invoke(ContainerInterface $container): HttplugEventStore $config = $container->get('config'); $config = $this->options($config, $this->configId); - if (isset($config['uri_factory'])) { - $uriFactory = $container->get($config['uri_factory']); - } else { - $uriFactory = UriFactoryDiscovery::find(); - } - $requestFactory = null; if (isset($config['request_factory'])) { @@ -87,7 +81,6 @@ public function __invoke(ContainerInterface $container): HttplugEventStore $container->get($config['message_factory']), $container->get($config['message_converter']), $container->get($config['http_client']), - $uriFactory->createUri($config['uri']), $requestFactory ); } @@ -109,7 +102,6 @@ public function mandatoryOptions(): iterable { return [ 'http_client', - 'uri', ]; } } diff --git a/src/Container/Projection/HttplugProjectionManagerFactory.php b/src/Container/Projection/HttplugProjectionManagerFactory.php index efa4b78..e0093ae 100644 --- a/src/Container/Projection/HttplugProjectionManagerFactory.php +++ b/src/Container/Projection/HttplugProjectionManagerFactory.php @@ -67,12 +67,6 @@ public function __invoke(ContainerInterface $container): HttplugProjectionManage $config = $container->get('config'); $config = $this->options($config, $this->configId); - if (isset($config['uri_factory'])) { - $uriFactory = $container->get($config['uri_factory']); - } else { - $uriFactory = UriFactoryDiscovery::find(); - } - $requestFactory = null; if (isset($config['request_factory'])) { @@ -81,7 +75,6 @@ public function __invoke(ContainerInterface $container): HttplugProjectionManage return new HttplugProjectionManager( $container->get($config['http_client']), - $uriFactory->createUri($config['uri']), $requestFactory ); } @@ -95,7 +88,6 @@ public function mandatoryOptions(): iterable { return [ 'http_client', - 'uri', ]; } } diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 81ed336..6412a55 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -30,7 +30,6 @@ use Prooph\EventStore\Stream; use Prooph\EventStore\StreamName; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\UriInterface; final class HttplugEventStore implements EventStore { @@ -49,11 +48,6 @@ final class HttplugEventStore implements EventStore */ private $httpClient; - /** - * @var UriInterface - */ - private $uri; - /** * @var RequestFactory */ @@ -63,13 +57,11 @@ public function __construct( MessageFactory $messageFactory, MessageConverter $messageConverter, HttpClient $httpClient, - UriInterface $uri, RequestFactory $requestFactory = null ) { $this->messageFactory = $messageFactory; $this->messageConverter = $messageConverter; $this->httpClient = $httpClient; - $this->uri = $uri; $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); } @@ -83,7 +75,7 @@ public function updateStreamMetadata(StreamName $streamName, array $newMetadata) $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('streammetadata/' . urlencode($streamName->toString())), + 'streammetadata/' . urlencode($streamName->toString()), [ 'Content-Type' => 'application/json', ], @@ -126,7 +118,7 @@ public function create(Stream $stream): void $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('stream/' . urlencode($streamName->toString())), + 'stream/' . urlencode($streamName->toString()), [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -162,7 +154,7 @@ public function delete(StreamName $streamName): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('delete/' . urlencode($streamName->toString())) + 'delete/' . urlencode($streamName->toString()) ); $response = $this->httpClient->sendRequest($request); @@ -184,7 +176,7 @@ public function fetchStreamMetadata(StreamName $streamName): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('streammetadata/' . urlencode($streamName->toString())), + 'streammetadata/' . urlencode($streamName->toString()), [ 'Accept' => 'application/json', ] @@ -215,7 +207,7 @@ public function hasStream(StreamName $streamName): bool { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('has-stream/' . urlencode($streamName->toString())) + 'has-stream/' . urlencode($streamName->toString()) ); $response = $this->httpClient->sendRequest($request); @@ -243,9 +235,8 @@ public function load( $count = PHP_INT_MAX; } - $uri = $this->uri - ->withPath('stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/forward/' . $count) - ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); + $uri = 'stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/forward/' . $count + . '?' . $this->buildQueryFromMetadataMatcher($metadataMatcher); $request = $this->requestFactory->createRequest( 'GET', @@ -286,9 +277,8 @@ public function loadReverse( $count = PHP_INT_MAX; } - $uri = $this->uri - ->withPath('stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/backward/' . $count) - ->withQuery($this->buildQueryFromMetadataMatcher($metadataMatcher)); + $uri = 'stream/' . urlencode($streamName->toString()) . '/' . $fromNumber . '/backward/' . $count + . '?' . $this->buildQueryFromMetadataMatcher($metadataMatcher); $request = $this->requestFactory->createRequest( 'GET', @@ -332,14 +322,14 @@ public function fetchStreamNames( } if (null !== $filter) { - $path = 'streams/' . urlencode($filter); + $uri = 'streams/' . urlencode($filter) . '?' . $query; } else { - $path = 'stream'; + $uri = 'streams?' . $query; } $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] @@ -380,11 +370,11 @@ public function fetchStreamNamesRegex( $query .= '&' . $limitPart; } - $path = 'streams-regex/' . urlencode($filter); + $uri = 'streams-regex/' . urlencode($filter) . '?' . $query; $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] @@ -414,14 +404,14 @@ public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset $query = 'limit=' . $limit . '&offset=' . $offset; if (null !== $filter) { - $path = 'categories/' . urlencode($filter); + $uri = 'categories/' . urlencode($filter) . '?' . $query; } else { - $path = 'categories'; + $uri = 'categories?' . $query; } $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] @@ -448,13 +438,11 @@ public function fetchCategoryNames(?string $filter, int $limit = 20, int $offset public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $offset = 0): array { - $query = 'limit=' . $limit . '&offset=' . $offset; - - $path = 'categories-regex/' . urlencode($filter); + $uri = 'categories-regex/' . urlencode($filter) . '?limit=' . $limit . '&offset=' . $offset; $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] diff --git a/src/Projection/HttplugProjectionManager.php b/src/Projection/HttplugProjectionManager.php index 45a7f4b..a0dc877 100644 --- a/src/Projection/HttplugProjectionManager.php +++ b/src/Projection/HttplugProjectionManager.php @@ -24,7 +24,6 @@ use Prooph\EventStore\Projection\Query; use Prooph\EventStore\Projection\ReadModel; use Prooph\EventStore\Projection\ReadModelProjector; -use Psr\Http\Message\UriInterface; final class HttplugProjectionManager implements ProjectionManager { @@ -33,11 +32,6 @@ final class HttplugProjectionManager implements ProjectionManager */ private $httpClient; - /** - * @var UriInterface - */ - private $uri; - /** * @var RequestFactory */ @@ -45,11 +39,9 @@ final class HttplugProjectionManager implements ProjectionManager public function __construct( HttpClient $httpClient, - UriInterface $uri, RequestFactory $requestFactory = null ) { $this->httpClient = $httpClient; - $this->uri = $uri; $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); } @@ -83,7 +75,7 @@ public function deleteProjection(string $name, bool $deleteEmittedEvents): void $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('projection/delete/' . urlencode($name) . '/' . $deleteEmittedEvents) + 'projection/delete/' . urlencode($name) . '/' . $deleteEmittedEvents ); $response = $this->httpClient->sendRequest($request); @@ -105,7 +97,7 @@ public function resetProjection(string $name): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('projection/reset/' . urlencode($name)) + 'projection/reset/' . urlencode($name) ); $response = $this->httpClient->sendRequest($request); @@ -127,7 +119,7 @@ public function stopProjection(string $name): void { $request = $this->requestFactory->createRequest( 'POST', - $this->uri->withPath('projection/stop/' . urlencode($name)) + 'projection/stop/' . urlencode($name) ); $response = $this->httpClient->sendRequest($request); @@ -150,14 +142,14 @@ public function fetchProjectionNames(?string $filter, int $limit = 20, int $offs $query = 'limit=' . $limit . '&offset=' . $offset; if (null !== $filter) { - $path = '/projections/' . urlencode($filter); + $uri = '/projections/' . urlencode($filter) . '?' . $query; } else { - $path = '/projections'; + $uri = '/projections?' . $query; } $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] @@ -184,13 +176,11 @@ public function fetchProjectionNames(?string $filter, int $limit = 20, int $offs public function fetchProjectionNamesRegex(string $regex, int $limit = 20, int $offset = 0): array { - $query = 'limit=' . $limit . '&offset=' . $offset; - - $path = 'projections-regex/' . urlencode($regex); + $uri = 'projections-regex/' . urlencode($regex) . '?limit=' . $limit . '&offset=' . $offset; $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath($path)->withQuery($query), + $uri, [ 'Accept' => 'application/json', ] @@ -219,7 +209,7 @@ public function fetchProjectionStatus(string $name): ProjectionStatus { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('projection/status/' . urlencode($name)), + 'projection/status/' . urlencode($name), [ 'Accept' => 'application/json', ] @@ -244,7 +234,7 @@ public function fetchProjectionStreamPositions(string $name): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('projection/stream-positions/' . urlencode($name)), + 'projection/stream-positions/' . urlencode($name), [ 'Accept' => 'application/json', ] @@ -275,7 +265,7 @@ public function fetchProjectionState(string $name): array { $request = $this->requestFactory->createRequest( 'GET', - $this->uri->withPath('projection/state/' . urlencode($name)), + 'projection/state/' . urlencode($name), [ 'Accept' => 'application/json', ] diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 2a0f021..9076d04 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -12,6 +12,7 @@ namespace ProophTest\HttplugEventStore; +use GuzzleHttp\Psr7\Uri; use Http\Client\HttpClient; use Http\Message\RequestFactory; use PHPUnit\Framework\TestCase; @@ -39,12 +40,6 @@ class HttplugEventStoreTest extends TestCase */ public function it_updates_stream_metadata(): void { - $finalUrl = $this->prophesize(UriInterface::class); - $finalUrl = $finalUrl->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('streammetadata/somename')->willReturn($finalUrl); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -52,7 +47,7 @@ public function it_updates_stream_metadata(): void $requestFactory ->createRequest( 'POST', - $finalUrl, + 'streammetadata/somename', [ 'Content-Type' => 'application/json', ], @@ -70,7 +65,6 @@ public function it_updates_stream_metadata(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -84,12 +78,6 @@ public function it_throws_exception_when_forbidden_to_update_metadata(): void { $this->expectException(NotAllowed::class); - $finalUrl = $this->prophesize(UriInterface::class); - $finalUrl = $finalUrl->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -97,7 +85,7 @@ public function it_throws_exception_when_forbidden_to_update_metadata(): void $requestFactory ->createRequest( 'POST', - $finalUrl, + 'streammetadata/unknown', [ 'Content-Type' => 'application/json', ], @@ -115,7 +103,6 @@ public function it_throws_exception_when_forbidden_to_update_metadata(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -129,12 +116,6 @@ public function it_throws_exception_on_unknown_error_when_updating_metadata(): v { $this->expectException(RuntimeException::class); - $finalUrl = $this->prophesize(UriInterface::class); - $finalUrl = $finalUrl->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -142,7 +123,7 @@ public function it_throws_exception_on_unknown_error_when_updating_metadata(): v $requestFactory ->createRequest( 'POST', - $finalUrl, + 'streammetadata/unknown', [ 'Content-Type' => 'application/json', ], @@ -160,7 +141,6 @@ public function it_throws_exception_on_unknown_error_when_updating_metadata(): v $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -179,7 +159,6 @@ public function it_throws_exception_when_cannot_json_encode_metadata_for_update( $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $this->prophesize(HttpClient::class)->reveal(), - $this->prophesize(UriInterface::class)->reveal(), $this->prophesize(RequestFactory::class)->reveal() ); @@ -193,12 +172,6 @@ public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_ { $this->expectException(StreamNotFound::class); - $finalUrl = $this->prophesize(UriInterface::class); - $finalUrl = $finalUrl->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('streammetadata/unknown')->willReturn($finalUrl); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -206,7 +179,7 @@ public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_ $requestFactory ->createRequest( 'POST', - $finalUrl, + 'streammetadata/unknown', [ 'Content-Type' => 'application/json', ], @@ -224,7 +197,6 @@ public function it_throws_stream_not_found_when_trying_to_update_unknown_stream_ $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -246,16 +218,6 @@ public function it_creates_stream(): void $messageData = $messageConverter->convertToArray($message); $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $finalUri2 = $this->prophesize(UriInterface::class); - $finalUri2 = $finalUri2->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('stream/somename')->willReturn($finalUri); - $uri->withPath('streammetadata/somename')->willReturn($finalUri2); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -266,7 +228,7 @@ public function it_creates_stream(): void $requestFactory ->createRequest( 'POST', - $finalUri, + 'stream/somename', [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -277,7 +239,7 @@ public function it_creates_stream(): void $requestFactory ->createRequest( 'POST', - $finalUri2, + 'streammetadata/somename', [ 'Content-Type' => 'application/json', ], @@ -299,7 +261,6 @@ public function it_creates_stream(): void $this->prophesize(MessageFactory::class)->reveal(), $messageConverter, $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -320,12 +281,6 @@ public function it_creates_stream_and_throws_error_on_400(): void $messageData = $messageConverter->convertToArray($message); $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('stream/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -333,7 +288,7 @@ public function it_creates_stream_and_throws_error_on_400(): void $requestFactory ->createRequest( 'POST', - $finalUri, + 'stream/somename', [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -352,7 +307,6 @@ public function it_creates_stream_and_throws_error_on_400(): void $this->prophesize(MessageFactory::class)->reveal(), $messageConverter, $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -372,12 +326,6 @@ public function it_cannot_create_stream_when_not_allowed(): void $messageData = $messageConverter->convertToArray($message); $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('stream/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -385,7 +333,7 @@ public function it_cannot_create_stream_when_not_allowed(): void $requestFactory ->createRequest( 'POST', - $finalUri, + 'stream/somename', [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -403,7 +351,6 @@ public function it_cannot_create_stream_when_not_allowed(): void $this->prophesize(MessageFactory::class)->reveal(), $messageConverter, $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -423,12 +370,6 @@ public function it_handles_unknown_errors_on_create(): void $messageData = $messageConverter->convertToArray($message); $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('stream/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -436,7 +377,7 @@ public function it_handles_unknown_errors_on_create(): void $requestFactory ->createRequest( 'POST', - $finalUri, + 'stream/somename', [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -454,7 +395,6 @@ public function it_handles_unknown_errors_on_create(): void $this->prophesize(MessageFactory::class)->reveal(), $messageConverter, $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -476,12 +416,6 @@ public function it_appends_to_stream_and_creates_it_automatically(): void $messageData = $messageConverter->convertToArray($message); $messageData['created_at'] = $messageData['created_at']->format('Y-m-d\TH:i:s.u'); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('stream/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -489,7 +423,7 @@ public function it_appends_to_stream_and_creates_it_automatically(): void $requestFactory ->createRequest( 'POST', - $finalUri, + 'stream/somename', [ 'Content-Type' => 'application/vnd.eventstore.atom+json', ], @@ -507,7 +441,6 @@ public function it_appends_to_stream_and_creates_it_automatically(): void $this->prophesize(MessageFactory::class)->reveal(), $messageConverter, $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -523,12 +456,6 @@ public function it_appends_to_stream_and_creates_it_automatically(): void */ public function it_deletes_stream(): void { - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('delete/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -536,7 +463,7 @@ public function it_deletes_stream(): void $requestFactory ->createRequest( 'POST', - $finalUri + 'delete/somename' ) ->willReturn($request); @@ -550,7 +477,6 @@ public function it_deletes_stream(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -564,12 +490,6 @@ public function it_cannot_delete_stream_when_not_found(): void { $this->expectException(StreamNotFound::class); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('delete/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -577,7 +497,7 @@ public function it_cannot_delete_stream_when_not_found(): void $requestFactory ->createRequest( 'POST', - $finalUri + 'delete/somename' ) ->willReturn($request); @@ -591,7 +511,6 @@ public function it_cannot_delete_stream_when_not_found(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -605,12 +524,6 @@ public function it_cannot_delete_stream_when_not_allowed(): void { $this->expectException(NotAllowed::class); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('delete/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -618,7 +531,7 @@ public function it_cannot_delete_stream_when_not_allowed(): void $requestFactory ->createRequest( 'POST', - $finalUri + 'delete/somename' ) ->willReturn($request); @@ -632,7 +545,6 @@ public function it_cannot_delete_stream_when_not_allowed(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); @@ -646,12 +558,6 @@ public function it_handles_unknown_errors_on_delete(): void { $this->expectException(RuntimeException::class); - $finalUri = $this->prophesize(UriInterface::class); - $finalUri = $finalUri->reveal(); - - $uri = $this->prophesize(UriInterface::class); - $uri->withPath('delete/somename')->willReturn($finalUri); - $request = $this->prophesize(RequestInterface::class); $request = $request->reveal(); @@ -659,7 +565,7 @@ public function it_handles_unknown_errors_on_delete(): void $requestFactory ->createRequest( 'POST', - $finalUri + 'delete/somename' ) ->willReturn($request); @@ -673,7 +579,6 @@ public function it_handles_unknown_errors_on_delete(): void $this->prophesize(MessageFactory::class)->reveal(), $this->prophesize(MessageConverter::class)->reveal(), $httpClient->reveal(), - $uri->reveal(), $requestFactory->reveal() ); From 13a179b42a0ee60a1485a57aedee527bab37dd99 Mon Sep 17 00:00:00 2001 From: prolic Date: Sat, 15 Jul 2017 17:45:08 +0800 Subject: [PATCH 11/17] fix php cs issues (again) --- src/Container/HttplugEventStoreFactory.php | 1 - src/Container/Projection/HttplugProjectionManagerFactory.php | 1 - tests/HttplugEventStoreTest.php | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/Container/HttplugEventStoreFactory.php b/src/Container/HttplugEventStoreFactory.php index eb5eeaf..a7893f5 100644 --- a/src/Container/HttplugEventStoreFactory.php +++ b/src/Container/HttplugEventStoreFactory.php @@ -12,7 +12,6 @@ namespace Prooph\EventStore\Httplug\Container; -use Http\Discovery\UriFactoryDiscovery; use Interop\Config\ConfigurationTrait; use Interop\Config\ProvidesDefaultOptions; use Interop\Config\RequiresConfigId; diff --git a/src/Container/Projection/HttplugProjectionManagerFactory.php b/src/Container/Projection/HttplugProjectionManagerFactory.php index e0093ae..e2b2766 100644 --- a/src/Container/Projection/HttplugProjectionManagerFactory.php +++ b/src/Container/Projection/HttplugProjectionManagerFactory.php @@ -12,7 +12,6 @@ namespace Prooph\EventStore\Httplug\Container\Projection; -use Http\Discovery\UriFactoryDiscovery; use Interop\Config\ConfigurationTrait; use Interop\Config\RequiresConfigId; use Interop\Config\RequiresMandatoryOptions; diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 9076d04..0a78084 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -12,7 +12,6 @@ namespace ProophTest\HttplugEventStore; -use GuzzleHttp\Psr7\Uri; use Http\Client\HttpClient; use Http\Message\RequestFactory; use PHPUnit\Framework\TestCase; @@ -29,7 +28,6 @@ use ProophTest\EventStore\Mock\TestDomainEvent; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\UriInterface; class HttplugEventStoreTest extends TestCase { From 6cadd12d5830d58be62cf6fc2dc597293e8d86f3 Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 17 Jul 2017 16:40:14 +0800 Subject: [PATCH 12/17] add tests --- src/HttplugEventStore.php | 6 +- tests/HttplugEventStoreTest.php | 249 +++++++++++++++++++++++++++++++- 2 files changed, 246 insertions(+), 9 deletions(-) diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 6412a55..34005ff 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -186,7 +186,7 @@ public function fetchStreamMetadata(StreamName $streamName): array switch ($response->getStatusCode()) { case 200: - $metadata = json_decode($response->getBody()->getContents()); + $metadata = json_decode($response->getBody()->getContents(), true); if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException('Could not json decode response'); @@ -214,9 +214,9 @@ public function hasStream(StreamName $streamName): bool switch ($response->getStatusCode()) { case 200: - break; + return true; case 404: - throw StreamNotFound::with($streamName); + return false; case 403: case 405: throw new NotAllowed(); diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 0a78084..9e15e26 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -28,6 +28,7 @@ use ProophTest\EventStore\Mock\TestDomainEvent; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class HttplugEventStoreTest extends TestCase { @@ -71,8 +72,9 @@ public function it_updates_stream_metadata(): void /** * @test + * @dataProvider forbiddenStatusCodes */ - public function it_throws_exception_when_forbidden_to_update_metadata(): void + public function it_throws_exception_when_forbidden_to_update_metadata(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -92,7 +94,7 @@ public function it_throws_exception_when_forbidden_to_update_metadata(): void ->willReturn($request); $response = $this->prophesize(ResponseInterface::class); - $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); $httpClient = $this->prophesize(HttpClient::class); $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); @@ -313,8 +315,9 @@ public function it_creates_stream_and_throws_error_on_400(): void /** * @test + * @dataProvider forbiddenStatusCodes */ - public function it_cannot_create_stream_when_not_allowed(): void + public function it_cannot_create_stream_when_not_allowed(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -340,7 +343,7 @@ public function it_cannot_create_stream_when_not_allowed(): void ->willReturn($request); $response = $this->prophesize(ResponseInterface::class); - $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); $httpClient = $this->prophesize(HttpClient::class); $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); @@ -517,8 +520,9 @@ public function it_cannot_delete_stream_when_not_found(): void /** * @test + * @dataProvider forbiddenStatusCodes */ - public function it_cannot_delete_stream_when_not_allowed(): void + public function it_cannot_delete_stream_when_not_allowed(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -534,7 +538,7 @@ public function it_cannot_delete_stream_when_not_allowed(): void ->willReturn($request); $response = $this->prophesize(ResponseInterface::class); - $response->getStatusCode()->willReturn(405)->shouldBeCalled(); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); $httpClient = $this->prophesize(HttpClient::class); $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); @@ -584,4 +588,237 @@ public function it_handles_unknown_errors_on_delete(): void } // + + // + + /** + * @test + */ + public function it_fetches_stream_metadata(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streammetadata/somename', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $body = $this->prophesize(StreamInterface::class); + $body->getContents()->willReturn(json_encode(['foo' => 'bar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($body->reveal())->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $streamMetadata = $eventStore->fetchStreamMetadata(new StreamName('somename')); + + $this->assertSame(['foo' => 'bar'], $streamMetadata); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_not_allowed_when_forbidden_to_fetch_stream_metadata(int $forbidenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streammetadata/somename', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbidenStatusCode)->shouldBeCalled(); + $response->getBody()->shouldNotBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamMetadata(new StreamName('somename')); + } + + /** + * @test + */ + public function it_throws_stream_not_found_when_trying_to_fetch_unknown_stream_metadata(): void + { + $this->expectException(StreamNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streammetadata/somename', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + $response->getBody()->shouldNotBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamMetadata(new StreamName('somename')); + } + + // + + // + + /** + * @test + */ + public function it_returns_true_when_asking_for_existing_stream(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'has-stream/somename' + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $this->assertTrue($eventStore->hasStream(new StreamName('somename'))); + } + + /** + * @test + */ + public function it_returns_false_when_asking_for_non_existing_stream(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'has-stream/somename' + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $this->assertFalse($eventStore->hasStream(new StreamName('somename'))); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_not_allowed_when_forbidden_to_ask_for_existince_of_a_stream(int $forbidenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'has-stream/somename' + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbidenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->hasStream(new StreamName('somename')); + } + + // + + public function forbiddenStatusCodes(): array + { + return [ + [403], + [405], + ]; + } } From cf12dac45136d6fc8bf2da527112c061002bd497 Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 24 Jul 2017 18:46:17 +0800 Subject: [PATCH 13/17] add more tests --- src/HttplugEventStore.php | 6 +- tests/HttplugEventStoreTest.php | 663 ++++++++++++++++++++++++++++++++ 2 files changed, 668 insertions(+), 1 deletion(-) diff --git a/src/HttplugEventStore.php b/src/HttplugEventStore.php index 34005ff..784415d 100644 --- a/src/HttplugEventStore.php +++ b/src/HttplugEventStore.php @@ -467,8 +467,12 @@ public function fetchCategoryNamesRegex(string $filter, int $limit = 20, int $of } } - private function buildQueryFromMetadataMatcher(MetadataMatcher $metadataMatcher): string + private function buildQueryFromMetadataMatcher(MetadataMatcher $metadataMatcher = null): string { + if (null === $metadataMatcher) { + return ''; + } + $params = []; foreach ($metadataMatcher->data() as $key => $match) { diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 9e15e26..506e424 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -15,6 +15,7 @@ use Http\Client\HttpClient; use Http\Message\RequestFactory; use PHPUnit\Framework\TestCase; +use Prooph\Common\Messaging\FQCNMessageFactory; use Prooph\Common\Messaging\MessageConverter; use Prooph\Common\Messaging\MessageFactory; use Prooph\Common\Messaging\NoOpMessageConverter; @@ -23,6 +24,9 @@ use Prooph\EventStore\Exception\StreamNotFound; use Prooph\EventStore\Httplug\Exception\NotAllowed; use Prooph\EventStore\Httplug\HttplugEventStore; +use Prooph\EventStore\Metadata\FieldType; +use Prooph\EventStore\Metadata\MetadataMatcher; +use Prooph\EventStore\Metadata\Operator; use Prooph\EventStore\Stream; use Prooph\EventStore\StreamName; use ProophTest\EventStore\Mock\TestDomainEvent; @@ -709,6 +713,43 @@ public function it_throws_stream_not_found_when_trying_to_fetch_unknown_stream_m $eventStore->fetchStreamMetadata(new StreamName('somename')); } + /** + * @test + */ + public function it_throws_exception_on_unknown_error_fetching_stream_metadata(): void + { + $this->expectException(RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streammetadata/somename', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamMetadata(new StreamName('somename')); + } + // // @@ -814,6 +855,618 @@ public function it_throws_not_allowed_when_forbidden_to_ask_for_existince_of_a_s // + // + + /** + * @test + * @dataProvider getTestEvents + */ + public function it_loads_stream(array $testEvents): void + { + $testEvent1 = current($testEvents); + next($testEvents); + $testEvent2 = current($testEvents); + next($testEvents); + $testEvent3 = current($testEvents); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/foo/1/forward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $testEvent1Array = $testEvent1->toArray(); + $testEvent1Array['created_at'] = $testEvent1->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent2Array = $testEvent2->toArray(); + $testEvent2Array['created_at'] = $testEvent2->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent3Array = $testEvent3->toArray(); + $testEvent3Array['created_at'] = $testEvent3->createdAt()->format('Y-m-d\TH:i:s.u'); + + $content = [ + 'title' => 'Event Stream \'foo\'', + 'id' => 'http://localhost:8080/stream/foo', + 'streamName' => 'foo', + '_links' => [ + [ + 'uri' => 'http://localhost:8080/stream/foo', + 'relation' => 'self', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/1/forward/3', + 'relation' => 'first', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/head/backward/3', + 'relation' => 'last', + ], + ], + 'entries' => [ + $testEvent1Array, + $testEvent2Array, + $testEvent3Array, + ], + ]; + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode($content))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + new FQCNMessageFactory(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $events = $eventStore->load(new StreamName('foo')); + + $this->assertInstanceOf(\Iterator::class, $events); + + $this->assertTrue($testEvent1->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent1->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertTrue($testEvent2->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent2->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertTrue($testEvent3->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent3->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertNull($events->current()); + } + + /** + * @test + * @dataProvider getTestEvents + */ + public function it_loads_stream_with_metadata_matcher_limit_and_offset(array $testEvents): void + { + $testEvent1 = current($testEvents); + next($testEvents); + $testEvent2 = current($testEvents); + next($testEvents); + $testEvent3 = current($testEvents); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/foo/2/forward/3?meta_0_field=key&meta_0_operator=EQUALS&meta_0_value=value&property_1_field=uuid&property_1_operator=EQUALS&property_1_value=' . $testEvent3->uuid()->toString(), + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $testEvent1Array = $testEvent1->toArray(); + $testEvent1Array['created_at'] = $testEvent1->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent2Array = $testEvent2->toArray(); + $testEvent2Array['created_at'] = $testEvent2->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent3Array = $testEvent3->toArray(); + $testEvent3Array['created_at'] = $testEvent3->createdAt()->format('Y-m-d\TH:i:s.u'); + + $content = [ + 'title' => 'Event Stream \'foo\'', + 'id' => 'http://localhost:8080/stream/foo', + 'streamName' => 'foo', + '_links' => [ + [ + 'uri' => 'http://localhost:8080/stream/foo', + 'relation' => 'self', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/2/forward/3', + 'relation' => 'first', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/head/backward/3', + 'relation' => 'last', + ], + ], + 'entries' => [ + $testEvent3Array, + ], + ]; + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode($content))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + new FQCNMessageFactory(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $metadataMatcher = new MetadataMatcher(); + $metadataMatcher = $metadataMatcher->withMetadataMatch('key', Operator::EQUALS(), 'value'); + $metadataMatcher = $metadataMatcher->withMetadataMatch('uuid', Operator::EQUALS(), $testEvent3->uuid()->toString(), FieldType::MESSAGE_PROPERTY()); + + $events = $eventStore->load(new StreamName('foo'), 2, 3, $metadataMatcher); + + $this->assertInstanceOf(\Iterator::class, $events); + + $this->assertTrue($testEvent3->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent3->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertNull($events->current()); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_not_allowed_when_load_is_forbidden(int $forbidenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/somename/1/forward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbidenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->load(new StreamName('somename')); + } + + /** + * @test + */ + public function it_throws_stream_not_found_on_load(): void + { + $this->expectException(StreamNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/unknown/1/forward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->load(new StreamName('unknown')); + } + + /** + * @test + */ + public function it_throws_exception_on_unknown_error_when_loading(): void + { + $this->expectException(RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/somename/1/forward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->load(new StreamName('somename')); + } + + // + + // + + /** + * @test + * @dataProvider getTestEvents + */ + public function it_loads_stream_reverse(array $testEvents): void + { + $testEvent1 = current($testEvents); + next($testEvents); + $testEvent2 = current($testEvents); + next($testEvents); + $testEvent3 = current($testEvents); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/foo/' . PHP_INT_MAX . '/backward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $testEvent1Array = $testEvent1->toArray(); + $testEvent1Array['created_at'] = $testEvent1->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent2Array = $testEvent2->toArray(); + $testEvent2Array['created_at'] = $testEvent2->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent3Array = $testEvent3->toArray(); + $testEvent3Array['created_at'] = $testEvent3->createdAt()->format('Y-m-d\TH:i:s.u'); + + $content = [ + 'title' => 'Event Stream \'foo\'', + 'id' => 'http://localhost:8080/stream/foo', + 'streamName' => 'foo', + '_links' => [ + [ + 'uri' => 'http://localhost:8080/stream/foo', + 'relation' => 'self', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/1/forward/3', + 'relation' => 'first', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/head/backward/3', + 'relation' => 'last', + ], + ], + 'entries' => [ + $testEvent3Array, + $testEvent2Array, + $testEvent1Array, + ], + ]; + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode($content))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + new FQCNMessageFactory(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $events = $eventStore->loadReverse(new StreamName('foo')); + + $this->assertInstanceOf(\Iterator::class, $events); + + $this->assertTrue($testEvent3->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent3->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertTrue($testEvent2->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent2->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertTrue($testEvent1->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent1->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertNull($events->current()); + } + + /** + * @test + * @dataProvider getTestEvents + */ + public function it_loads_stream_reverse_with_metadata_matcher_limit_and_offset(array $testEvents): void + { + $testEvent1 = current($testEvents); + next($testEvents); + $testEvent2 = current($testEvents); + next($testEvents); + $testEvent3 = current($testEvents); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/foo/3/backward/3?meta_0_field=key&meta_0_operator=EQUALS&meta_0_value=value&property_1_field=uuid&property_1_operator=EQUALS&property_1_value=' . $testEvent3->uuid()->toString(), + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $testEvent1Array = $testEvent1->toArray(); + $testEvent1Array['created_at'] = $testEvent1->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent2Array = $testEvent2->toArray(); + $testEvent2Array['created_at'] = $testEvent2->createdAt()->format('Y-m-d\TH:i:s.u'); + + $testEvent3Array = $testEvent3->toArray(); + $testEvent3Array['created_at'] = $testEvent3->createdAt()->format('Y-m-d\TH:i:s.u'); + + $content = [ + 'title' => 'Event Stream \'foo\'', + 'id' => 'http://localhost:8080/stream/foo', + 'streamName' => 'foo', + '_links' => [ + [ + 'uri' => 'http://localhost:8080/stream/foo', + 'relation' => 'self', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/2/forward/3', + 'relation' => 'first', + ], + [ + 'uri' => 'http://localhost:8080/stream/foo/head/backward/3', + 'relation' => 'last', + ], + ], + 'entries' => [ + $testEvent3Array, + ], + ]; + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode($content))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + new FQCNMessageFactory(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $metadataMatcher = new MetadataMatcher(); + $metadataMatcher = $metadataMatcher->withMetadataMatch('key', Operator::EQUALS(), 'value'); + $metadataMatcher = $metadataMatcher->withMetadataMatch('uuid', Operator::EQUALS(), $testEvent3->uuid()->toString(), FieldType::MESSAGE_PROPERTY()); + + $events = $eventStore->loadReverse(new StreamName('foo'), 3, 3, $metadataMatcher); + + $this->assertInstanceOf(\Iterator::class, $events); + + $this->assertTrue($testEvent3->uuid()->equals($events->current()->uuid())); + $this->assertSame($testEvent3->payload(), $events->current()->payload()); + + $events->next(); + + $this->assertNull($events->current()); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_not_allowed_when_load_reverse_is_forbidden(int $forbidenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/somename/' . PHP_INT_MAX . '/backward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbidenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->loadReverse(new StreamName('somename')); + } + + /** + * @test + */ + public function it_throws_stream_not_found_on_load_reverse(): void + { + $this->expectException(StreamNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/unknown/' . PHP_INT_MAX . '/backward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->loadReverse(new StreamName('unknown')); + } + + /** + * @test + */ + public function it_throws_exception_on_unknown_error_when_loading_reverse(): void + { + $this->expectException(RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'stream/somename/' . PHP_INT_MAX . '/backward/' . PHP_INT_MAX . '?', + [ + 'Accept' => 'application/vnd.eventstore.atom+json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->loadReverse(new StreamName('somename')); + } + + // + public function forbiddenStatusCodes(): array { return [ @@ -821,4 +1474,14 @@ public function forbiddenStatusCodes(): array [405], ]; } + + public function getTestEvents(): array + { + $event1 = TestDomainEvent::with(['foo' => 'bar'], 1); + $event2 = TestDomainEvent::with(['foo' => 'baz'], 2); + $event3 = TestDomainEvent::with(['foo' => 'bam'], 3); + $event3 = $event3->withAddedMetadata('key', 'value'); + + return [[[$event1, $event2, $event3]]]; + } } From b2c89c9beb17e3dff816cd47c2978bd5dc8d24f5 Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 24 Jul 2017 20:42:43 +0800 Subject: [PATCH 14/17] add even more tests --- tests/HttplugEventStoreTest.php | 704 +++++++++++++++++++++++++++++++- 1 file changed, 694 insertions(+), 10 deletions(-) diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index 506e424..e2db9f2 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -321,7 +321,7 @@ public function it_creates_stream_and_throws_error_on_400(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_cannot_create_stream_when_not_allowed(int $forbiddenStatusCode): void + public function it_throws_exception_when_forbidden_to_create_stream(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -526,7 +526,7 @@ public function it_cannot_delete_stream_when_not_found(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_cannot_delete_stream_when_not_allowed(int $forbiddenStatusCode): void + public function it_throws_exception_when_forbidden_to_delete(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -640,7 +640,7 @@ public function it_fetches_stream_metadata(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_throws_not_allowed_when_forbidden_to_fetch_stream_metadata(int $forbidenStatusCode): void + public function it_throws_exception_when_forbidden_to_fetch_stream_metadata(int $forbidenStatusCode): void { $this->expectException(NotAllowed::class); @@ -678,7 +678,7 @@ public function it_throws_not_allowed_when_forbidden_to_fetch_stream_metadata(in /** * @test */ - public function it_throws_stream_not_found_when_trying_to_fetch_unknown_stream_metadata(): void + public function it_throws_exception_when_forbidden_to_fetch_stream_metadata(): void { $this->expectException(StreamNotFound::class); @@ -716,7 +716,7 @@ public function it_throws_stream_not_found_when_trying_to_fetch_unknown_stream_m /** * @test */ - public function it_throws_exception_on_unknown_error_fetching_stream_metadata(): void + public function it_handles_unknown_errors_on_fetch_stream_metadata(): void { $this->expectException(RuntimeException::class); @@ -822,7 +822,7 @@ public function it_returns_false_when_asking_for_non_existing_stream(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_throws_not_allowed_when_forbidden_to_ask_for_existince_of_a_stream(int $forbidenStatusCode): void + public function it_throws_exception_when_forbidden_to_call_has_stream(int $forbidenStatusCode): void { $this->expectException(NotAllowed::class); @@ -853,6 +853,40 @@ public function it_throws_not_allowed_when_forbidden_to_ask_for_existince_of_a_s $eventStore->hasStream(new StreamName('somename')); } + /** + * @test + */ + public function it_handles_unknown_errors_on_has_stream(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'has-stream/somename' + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->hasStream(new StreamName('somename')); + } + // // @@ -1051,7 +1085,7 @@ public function it_loads_stream_with_metadata_matcher_limit_and_offset(array $te * @test * @dataProvider forbiddenStatusCodes */ - public function it_throws_not_allowed_when_load_is_forbidden(int $forbidenStatusCode): void + public function it_throws_exception_when_forbidden_to_load(int $forbidenStatusCode): void { $this->expectException(NotAllowed::class); @@ -1125,7 +1159,7 @@ public function it_throws_stream_not_found_on_load(): void /** * @test */ - public function it_throws_exception_on_unknown_error_when_loading(): void + public function it_handles_unknown_errors_on_load(): void { $this->expectException(RuntimeException::class); @@ -1357,7 +1391,7 @@ public function it_loads_stream_reverse_with_metadata_matcher_limit_and_offset(a * @test * @dataProvider forbiddenStatusCodes */ - public function it_throws_not_allowed_when_load_reverse_is_forbidden(int $forbidenStatusCode): void + public function it_throws_exception_when_forbidden_to_load_reverse(int $forbidenStatusCode): void { $this->expectException(NotAllowed::class); @@ -1431,7 +1465,7 @@ public function it_throws_stream_not_found_on_load_reverse(): void /** * @test */ - public function it_throws_exception_on_unknown_error_when_loading_reverse(): void + public function it_handles_unknown_errors_on_load_reverse(): void { $this->expectException(RuntimeException::class); @@ -1467,6 +1501,656 @@ public function it_throws_exception_on_unknown_error_when_loading_reverse(): voi // + // + + /** + * @test + */ + public function it_fetches_stream_names(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams?meta_0_field=key&meta_0_operator=EQUALS&meta_0_value=value&limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo", "bar"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $metadataMatcher = new MetadataMatcher(); + $metadataMatcher = $metadataMatcher->withMetadataMatch('key', Operator::EQUALS(), 'value'); + + $streamNames = $eventStore->fetchStreamNames(null, $metadataMatcher); + + $this->assertSame(['foo', 'bar'], $streamNames); + } + + /** + * @test + */ + public function it_fetches_stream_names_using_filter(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $streamNames = $eventStore->fetchStreamNames('foo', null, 30, 40); + + $this->assertSame(['foo'], $streamNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_exception_when_forbidden_to_fetch_stream_names(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamNames('foo', null, 30, 40); + } + + /** + * @test + */ + public function it_handles_unknown_errors_on_fetch_stream_names(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamNames('foo', null, 30, 40); + } + + // + + // + + /** + * @test + */ + public function it_fetches_stream_names_regex(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams-regex/' . urlencode('^foo') . '?meta_0_field=key&meta_0_operator=EQUALS&meta_0_value=value&limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo", "foobar"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $metadataMatcher = new MetadataMatcher(); + $metadataMatcher = $metadataMatcher->withMetadataMatch('key', Operator::EQUALS(), 'value'); + + $streamNames = $eventStore->fetchStreamNamesRegex('^foo', $metadataMatcher); + + $this->assertSame(['foo', 'foobar'], $streamNames); + } + + /** + * @test + */ + public function it_fetches_stream_names_regex_using_limit_and_offset(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $streamNames = $eventStore->fetchStreamNamesRegex('^foo', null, 30, 40); + + $this->assertSame(['foo'], $streamNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_exception_when_forbidden_to_fetch_stream_names_regex(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamNamesRegex('^foo', null, 30, 40); + } + + /** + * @test + */ + public function it_handles_unknown_errors_on_fetch_stream_names_regex(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'streams-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchStreamNamesRegex('^foo', null, 30, 40); + } + + // + + // + + /** + * @test + */ + public function it_fetches_category_names(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo", "bar"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $categoryNames = $eventStore->fetchCategoryNames(null); + + $this->assertSame(['foo', 'bar'], $categoryNames); + } + + /** + * @test + */ + public function it_fetches_category_names_using_filter(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $categoryNames = $eventStore->fetchCategoryNames('foo', 30, 40); + + $this->assertSame(['foo'], $categoryNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_exception_when_forbidden_to_fetch_category_names(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchCategoryNames('foo', 30, 40); + } + + /** + * @test + */ + public function it_throws_exception_on_unknown_error_when_fetch_category_names(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchCategoryNames('foo', 30, 40); + } + + // + + // + + /** + * @test + */ + public function it_fetches_category_names_regex(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories-regex/' . urlencode('^foo') . '?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo", "foobar"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $streamNames = $eventStore->fetchCategoryNamesRegex('^foo'); + + $this->assertSame(['foo', 'foobar'], $streamNames); + } + + /** + * @test + */ + public function it_fetches_category_names_regex_using_limit_and_offset(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn('["foo"]')->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal()); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $categoryNames = $eventStore->fetchCategoryNamesRegex('^foo', 30, 40); + + $this->assertSame(['foo'], $categoryNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_throws_exception_when_forbidden_to_fetch_category_names_regex(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchCategoryNamesRegex('^foo', 30, 40); + } + + /** + * @test + */ + public function it_throws_exception_on_unknown_error_when_fetch_category_names_regex(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory + ->createRequest( + 'GET', + 'categories-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + ) + ->willReturn($request); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $eventStore = new HttplugEventStore( + $this->prophesize(MessageFactory::class)->reveal(), + $this->prophesize(MessageConverter::class)->reveal(), + $httpClient->reveal(), + $requestFactory->reveal() + ); + + $eventStore->fetchCategoryNamesRegex('^foo', 30, 40); + } + + // + public function forbiddenStatusCodes(): array { return [ From 33d2c7bcf7097d8ae73e039bbf9ca10cdc6f993c Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 24 Jul 2017 21:41:02 +0800 Subject: [PATCH 15/17] add HttplugProjectionManagerTest --- tests/HttplugEventStoreTest.php | 2 +- .../HttplugProjectionManagerTest.php | 426 ++++++++++++++++++ 2 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 tests/Projection/HttplugProjectionManagerTest.php diff --git a/tests/HttplugEventStoreTest.php b/tests/HttplugEventStoreTest.php index e2db9f2..345bd75 100644 --- a/tests/HttplugEventStoreTest.php +++ b/tests/HttplugEventStoreTest.php @@ -678,7 +678,7 @@ public function it_throws_exception_when_forbidden_to_fetch_stream_metadata(int /** * @test */ - public function it_throws_exception_when_forbidden_to_fetch_stream_metadata(): void + public function it_throws_stream_not_found_when_unknown_fetch_stream_metadata(): void { $this->expectException(StreamNotFound::class); diff --git a/tests/Projection/HttplugProjectionManagerTest.php b/tests/Projection/HttplugProjectionManagerTest.php new file mode 100644 index 0000000..30b3aed --- /dev/null +++ b/tests/Projection/HttplugProjectionManagerTest.php @@ -0,0 +1,426 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ProophTest\HttplugEventStore\Projection; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use PHPUnit\Framework\TestCase; +use Prooph\EventStore\Exception\ProjectionNotFound; +use Prooph\EventStore\Httplug\Exception\NotAllowed; +use Prooph\EventStore\Httplug\Projection\HttplugProjectionManager; +use Prooph\EventStore\Projection\ReadModel; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +class HttplugProjectionManagerTest extends TestCase +{ + /** + * @test + */ + public function it_cannot_create_query(): void + { + $this->expectException(\BadMethodCallException::class); + + $httpClient = $this->prophesize(HttpClient::class); + $requestFactory = $this->prophesize(RequestFactory::class); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->createQuery(); + } + + /** + * @test + */ + public function it_cannot_create_projection(): void + { + $this->expectException(\BadMethodCallException::class); + + $httpClient = $this->prophesize(HttpClient::class); + $requestFactory = $this->prophesize(RequestFactory::class); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->createProjection('test'); + } + + /** + * @test + */ + public function it_cannot_create_read_model_projection(): void + { + $this->expectException(\BadMethodCallException::class); + + $httpClient = $this->prophesize(HttpClient::class); + $requestFactory = $this->prophesize(RequestFactory::class); + $readModel = $this->prophesize(ReadModel::class); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->createReadModelProjection('test', $readModel->reveal()); + } + + /** + * @test + */ + public function it_deletes_projection_without_emitted_events(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/delete/somename/false' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->deleteProjection('somename', false); + } + + /** + * @test + */ + public function it_deletes_projection_with_emitted_events(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/delete/somename/true' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->deleteProjection('somename', true); + } + + /** + * @test + */ + public function it_cannot_delete_non_existing_projection(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/delete/somename/true' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->deleteProjection('somename', true); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_delete_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/delete/somename/true' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->deleteProjection('somename', true); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_delete_projection(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/delete/somename/true' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->deleteProjection('somename', true); + } + + /** + * @test + */ + public function it_resets_projection(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/reset/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->resetProjection('somename'); + } + + /** + * @test + */ + public function it_cannot_reset_non_existing_projection(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/reset/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->resetProjection('somename'); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_reset_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/reset/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->resetProjection('somename'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_reset_projection(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/reset/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->resetProjection('somename'); + } + + /** + * @test + */ + public function it_stops_projection(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(204)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/stop/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->stopProjection('somename'); + } + + /** + * @test + */ + public function it_cannot_stop_non_existing_projection(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/stop/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->stopProjection('somename'); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_stop_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/stop/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->stopProjection('somename'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_stop_projection(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'POST', + 'projection/stop/somename' + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->stopProjection('somename'); + } + + public function forbiddenStatusCodes(): array + { + return [ + [403], + [405], + ]; + } +} From 05ba0e8ea6b396a97ce726957aa1d779d84c5826 Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 24 Jul 2017 22:19:24 +0800 Subject: [PATCH 16/17] add more tests for projection manager --- src/Projection/HttplugProjectionManager.php | 4 +- .../HttplugProjectionManagerTest.php | 670 +++++++++++++++++- 2 files changed, 669 insertions(+), 5 deletions(-) diff --git a/src/Projection/HttplugProjectionManager.php b/src/Projection/HttplugProjectionManager.php index a0dc877..20cea55 100644 --- a/src/Projection/HttplugProjectionManager.php +++ b/src/Projection/HttplugProjectionManager.php @@ -142,9 +142,9 @@ public function fetchProjectionNames(?string $filter, int $limit = 20, int $offs $query = 'limit=' . $limit . '&offset=' . $offset; if (null !== $filter) { - $uri = '/projections/' . urlencode($filter) . '?' . $query; + $uri = 'projections/' . urlencode($filter) . '?' . $query; } else { - $uri = '/projections?' . $query; + $uri = 'projections?' . $query; } $request = $this->requestFactory->createRequest( diff --git a/tests/Projection/HttplugProjectionManagerTest.php b/tests/Projection/HttplugProjectionManagerTest.php index 30b3aed..2c30454 100644 --- a/tests/Projection/HttplugProjectionManagerTest.php +++ b/tests/Projection/HttplugProjectionManagerTest.php @@ -18,9 +18,11 @@ use Prooph\EventStore\Exception\ProjectionNotFound; use Prooph\EventStore\Httplug\Exception\NotAllowed; use Prooph\EventStore\Httplug\Projection\HttplugProjectionManager; +use Prooph\EventStore\Projection\ProjectionStatus; use Prooph\EventStore\Projection\ReadModel; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class HttplugProjectionManagerTest extends TestCase { @@ -70,6 +72,8 @@ public function it_cannot_create_read_model_projection(): void $projectionManager->createReadModelProjection('test', $readModel->reveal()); } + // + /** * @test */ @@ -151,7 +155,7 @@ public function it_cannot_delete_non_existing_projection(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_cannot_delete_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + public function it_cannot_delete_projection_when_forbidden(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -202,6 +206,10 @@ public function it_handles_unknown_error_on_delete_projection(): void $projectionManager->deleteProjection('somename', true); } + // + + // + /** * @test */ @@ -258,7 +266,7 @@ public function it_cannot_reset_non_existing_projection(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_cannot_reset_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + public function it_cannot_reset_projection_when_forbidden(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -309,6 +317,10 @@ public function it_handles_unknown_error_on_reset_projection(): void $projectionManager->resetProjection('somename'); } + // + + // + /** * @test */ @@ -365,7 +377,7 @@ public function it_cannot_stop_non_existing_projection(): void * @test * @dataProvider forbiddenStatusCodes */ - public function it_cannot_stop_non_existing_projection_when_forbidden(int $forbiddenStatusCode): void + public function it_cannot_stop_projection_when_forbidden(int $forbiddenStatusCode): void { $this->expectException(NotAllowed::class); @@ -416,6 +428,658 @@ public function it_handles_unknown_error_on_stop_projection(): void $projectionManager->stopProjection('somename'); } + // + + // + + /** + * @test + */ + public function it_fetches_projection_names(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['foo', 'bar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionNames = $projectionManager->fetchProjectionNames(null); + + $this->assertSame(['foo', 'bar'], $projectionNames); + } + + /** + * @test + */ + public function it_fetches_projection_names_using_filter_offset_and_limit(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['foo', 'foobar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections/foo?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionNames = $projectionManager->fetchProjectionNames('foo', 30, 40); + + $this->assertSame(['foo', 'foobar'], $projectionNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_fetch_projection_names_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionNames(null); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_fetch_projection_names(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionNames(null); + } + + // + + // + + /** + * @test + */ + public function it_fetches_projection_names_regex(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['foo', 'foobar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections-regex/' . urlencode('^foo') . '?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionNames = $projectionManager->fetchProjectionNamesRegex('^foo'); + + $this->assertSame(['foo', 'foobar'], $projectionNames); + } + + /** + * @test + */ + public function it_fetches_projection_names_regex_offset_and_limit(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['foo', 'foobar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections-regex/' . urlencode('^foo') . '?limit=30&offset=40', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionNames = $projectionManager->fetchProjectionNamesRegex('^foo', 30, 40); + + $this->assertSame(['foo', 'foobar'], $projectionNames); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_fetch_projection_names_regex_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections-regex/' . urlencode('^foo') . '?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionNamesRegex('^foo'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_fetch_projection_names_regex(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projections-regex/' . urlencode('^foo') . '?limit=20&offset=0', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionNamesRegex('^foo'); + } + + // + + // + + /** + * @test + */ + public function it_fetches_projection_status(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getReasonPhrase()->willReturn(ProjectionStatus::RUNNING()->getName())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/status/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $status = $projectionManager->fetchProjectionStatus('somename'); + + $this->assertTrue(ProjectionStatus::RUNNING()->is($status)); + } + + /** + * @test + */ + public function it_cannot_unknown_fetch_projection_status(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/status/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStatus('somename'); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_fetch_projection_status_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/status/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStatus('somename'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_fetch_projection_status(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/status/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStatus('somename'); + } + + // + + // + + /** + * @test + */ + public function it_fetches_projection_stream_positions(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['stream1' => 200, 'stream2' => 400]))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/stream-positions/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $streamPositions = $projectionManager->fetchProjectionStreamPositions('somename'); + + $this->assertSame(['stream1' => 200, 'stream2' => 400], $streamPositions); + } + + /** + * @test + */ + public function it_cannot_unknown_fetch_projection_stream_positions(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/stream-positions/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStreamPositions('somename'); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_fetch_projection_stream_positions_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/stream-positions/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStreamPositions('somename'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_fetch_projection_stream_positions(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/stream-positions/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionStreamPositions('somename'); + } + + // + + // + + /** + * @test + */ + public function it_fetches_projection_state(): void + { + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $stream = $this->prophesize(StreamInterface::class); + $stream->getContents()->willReturn(json_encode(['foo' => 'bar']))->shouldBeCalled(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(200)->shouldBeCalled(); + $response->getBody()->willReturn($stream->reveal())->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/state/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $state = $projectionManager->fetchProjectionState('somename'); + + $this->assertSame(['foo' => 'bar'], $state); + } + + /** + * @test + */ + public function it_cannot_unknown_fetch_projection_state(): void + { + $this->expectException(ProjectionNotFound::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(404)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/state/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionState('somename'); + } + + /** + * @test + * @dataProvider forbiddenStatusCodes + */ + public function it_cannot_fetch_projection_state_when_forbidden(int $forbiddenStatusCode): void + { + $this->expectException(NotAllowed::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn($forbiddenStatusCode)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/state/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionState('somename'); + } + + /** + * @test + */ + public function it_handles_unknown_error_on_fetch_projection_state(): void + { + $this->expectException(\RuntimeException::class); + + $request = $this->prophesize(RequestInterface::class); + $request = $request->reveal(); + + $response = $this->prophesize(ResponseInterface::class); + $response->getStatusCode()->willReturn(500)->shouldBeCalled(); + + $requestFactory = $this->prophesize(RequestFactory::class); + $requestFactory->createRequest( + 'GET', + 'projection/state/somename', + [ + 'Accept' => 'application/json', + ] + )->willReturn($request); + + $httpClient = $this->prophesize(HttpClient::class); + $httpClient->sendRequest($request)->willReturn($response->reveal())->shouldBeCalled(); + + $projectionManager = new HttplugProjectionManager($httpClient->reveal(), $requestFactory->reveal()); + + $projectionManager->fetchProjectionState('somename'); + } + + // + public function forbiddenStatusCodes(): array { return [ From e296d5246d01a1d41a29c91e28d6b05070e1e631 Mon Sep 17 00:00:00 2001 From: prolic Date: Mon, 24 Jul 2017 23:14:59 +0800 Subject: [PATCH 17/17] add container factory tests --- .../HttplugEventStoreFactoryTest.php | 97 +++++++++++++++++++ .../HttplugProjectionManagerFactoryTest.php | 91 +++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 tests/Container/HttplugEventStoreFactoryTest.php create mode 100644 tests/Container/Projection/HttplugProjectionManagerFactoryTest.php diff --git a/tests/Container/HttplugEventStoreFactoryTest.php b/tests/Container/HttplugEventStoreFactoryTest.php new file mode 100644 index 0000000..a2a7d59 --- /dev/null +++ b/tests/Container/HttplugEventStoreFactoryTest.php @@ -0,0 +1,97 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ProophTest\HttplugEventStore\Container; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use PHPUnit\Framework\TestCase; +use Prooph\Common\Messaging\FQCNMessageFactory; +use Prooph\Common\Messaging\NoOpMessageConverter; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Httplug\Container\HttplugEventStoreFactory; +use Prooph\EventStore\Httplug\HttplugEventStore; +use Psr\Container\ContainerInterface; + +class HttplugEventStoreFactoryTest extends TestCase +{ + /** + * @test + */ + public function it_creates_httplug_event_store(): void + { + $config = [ + 'prooph' => [ + 'event_store' => [ + 'default' => [ + 'http_client' => 'client', + 'request_factory' => 'requestFactory', + ], + ], + ], + ]; + + $container = $this->prophesize(ContainerInterface::class); + $container->get('config')->willReturn($config)->shouldBeCalled(); + $container->get(FQCNMessageFactory::class)->willReturn(new FQCNMessageFactory())->shouldBeCalled(); + $container->get(NoOpMessageConverter::class)->willReturn(new NoOpMessageConverter())->shouldBeCalled(); + $container->get('client')->willReturn($this->prophesize(HttpClient::class)->reveal())->shouldBeCalled(); + $container->get('requestFactory')->willReturn($this->prophesize(RequestFactory::class)->reveal())->shouldBeCalled(); + + $factory = new HttplugEventStoreFactory(); + $eventStore = $factory($container->reveal()); + + $this->assertInstanceOf(HttplugEventStore::class, $eventStore); + } + + /** + * @test + */ + public function it_creates_httplug_event_store_using_callstatic(): void + { + $config = [ + 'prooph' => [ + 'event_store' => [ + 'default' => [ + 'http_client' => 'client', + 'request_factory' => 'requestFactory', + ], + ], + ], + ]; + + $container = $this->prophesize(ContainerInterface::class); + $container->get('config')->willReturn($config)->shouldBeCalled(); + $container->get(FQCNMessageFactory::class)->willReturn(new FQCNMessageFactory())->shouldBeCalled(); + $container->get(NoOpMessageConverter::class)->willReturn(new NoOpMessageConverter())->shouldBeCalled(); + $container->get('client')->willReturn($this->prophesize(HttpClient::class)->reveal())->shouldBeCalled(); + $container->get('requestFactory')->willReturn($this->prophesize(RequestFactory::class)->reveal())->shouldBeCalled(); + + $name = 'default'; + + $eventStore = HttplugEventStoreFactory::$name($container->reveal()); + + $this->assertInstanceOf(HttplugEventStore::class, $eventStore); + } + + /** + * @test + */ + public function it_throws_invalid_argument_exception_when_invalid_container_given(): void + { + $this->expectException(InvalidArgumentException::class); + + $name = 'default'; + + HttplugEventStoreFactory::$name('invalid'); + } +} diff --git a/tests/Container/Projection/HttplugProjectionManagerFactoryTest.php b/tests/Container/Projection/HttplugProjectionManagerFactoryTest.php new file mode 100644 index 0000000..8f672bb --- /dev/null +++ b/tests/Container/Projection/HttplugProjectionManagerFactoryTest.php @@ -0,0 +1,91 @@ + + * (c) 2017-2017 Sascha-Oliver Prolic + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ProophTest\HttplugEventStore\Container\Projection; + +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use PHPUnit\Framework\TestCase; +use Prooph\EventStore\Exception\InvalidArgumentException; +use Prooph\EventStore\Httplug\Container\Projection\HttplugProjectionManagerFactory; +use Prooph\EventStore\Httplug\Projection\HttplugProjectionManager; +use Psr\Container\ContainerInterface; + +class HttplugProjectionManagerFactoryTest extends TestCase +{ + /** + * @test + */ + public function it_creates_httplug_projection_manager(): void + { + $config = [ + 'prooph' => [ + 'projection_manager' => [ + 'default' => [ + 'http_client' => 'client', + 'request_factory' => 'requestFactory', + ], + ], + ], + ]; + + $container = $this->prophesize(ContainerInterface::class); + $container->get('config')->willReturn($config)->shouldBeCalled(); + $container->get('client')->willReturn($this->prophesize(HttpClient::class)->reveal())->shouldBeCalled(); + $container->get('requestFactory')->willReturn($this->prophesize(RequestFactory::class)->reveal())->shouldBeCalled(); + + $factory = new HttplugProjectionManagerFactory(); + $eventStore = $factory($container->reveal()); + + $this->assertInstanceOf(HttplugProjectionManager::class, $eventStore); + } + + /** + * @test + */ + public function it_creates_httplug_projection_manager_using_callstatic(): void + { + $config = [ + 'prooph' => [ + 'projection_manager' => [ + 'default' => [ + 'http_client' => 'client', + 'request_factory' => 'requestFactory', + ], + ], + ], + ]; + + $container = $this->prophesize(ContainerInterface::class); + $container->get('config')->willReturn($config)->shouldBeCalled(); + $container->get('client')->willReturn($this->prophesize(HttpClient::class)->reveal())->shouldBeCalled(); + $container->get('requestFactory')->willReturn($this->prophesize(RequestFactory::class)->reveal())->shouldBeCalled(); + + $name = 'default'; + + $projectionManager = HttplugProjectionManagerFactory::$name($container->reveal()); + + $this->assertInstanceOf(HttplugProjectionManager::class, $projectionManager); + } + + /** + * @test + */ + public function it_throws_invalid_argument_exception_when_invalid_container_given(): void + { + $this->expectException(InvalidArgumentException::class); + + $name = 'default'; + + HttplugProjectionManagerFactory::$name('invalid'); + } +}