From 7a911f3178756c0fa5fa9c23de76ecd83e4494e0 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 16 Jul 2021 15:23:45 +0200 Subject: [PATCH 1/5] Update topic to use new pubsub --- src/index.php | 116 ++++++++++++++++++------------------- src/lib/PubSub/Publish.php | 4 +- src/lib/PubSub/Topic.php | 32 ++++++---- 3 files changed, 80 insertions(+), 72 deletions(-) diff --git a/src/index.php b/src/index.php index d482aec..00ba327 100644 --- a/src/index.php +++ b/src/index.php @@ -1,11 +1,10 @@ [SimpleActor::class], + 'dapr.actors' => [SimpleActor::class], ] ) ); @@ -61,14 +59,14 @@ public function increment(int $amount = 1): void $app->get( '/test/actors', function (ProxyFactory $proxyFactory, DaprClient $client, LoggerInterface $logger) { - $id = uniqid(prefix: 'actor_'); + $id = uniqid(prefix: 'actor_'); $reference = new ActorReference($id, 'SimpleActor'); /** * @var ISimpleActor|IActor $actor */ $actor = $reference->bind(ISimpleActor::class, $proxyFactory); - $body = []; + $body = []; $logger->critical('Created actor proxy'); $body = assert_equals($body, 0, $actor->get_count(), 'Empty actor should have no data'); @@ -78,7 +76,7 @@ function (ProxyFactory $proxyFactory, DaprClient $client, LoggerInterface $logge // get the actor proxy again $reference = ActorReference::get($actor); - $actor = $reference->bind(ISimpleActor::class, $proxyFactory); + $actor = $reference->bind(ISimpleActor::class, $proxyFactory); $reminder = new Reminder( name: 'increment', @@ -89,7 +87,7 @@ function (ProxyFactory $proxyFactory, DaprClient $client, LoggerInterface $logge $actor->create_reminder($reminder, $client); $logger->critical('Created reminder'); sleep(2); - $body = assert_equals($body, 3, $actor->get_count(), 'Reminder should increment'); + $body = assert_equals($body, 3, $actor->get_count(), 'Reminder should increment'); $read_reminder = $actor->get_reminder('increment', $client); $logger->critical('Got reminder'); $body = assert_equals( @@ -117,13 +115,13 @@ function (ProxyFactory $proxyFactory, DaprClient $client, LoggerInterface $logge $actor->delete_timer('nope', $client); $logger->critical('Cleaned up'); - $object = new SimpleObject(); + $object = new SimpleObject(); $object->bar = ['hello', 'world']; $object->foo = "hello world"; $actor->set_object($object); $saved_object = $actor->get_object(); - $body = assert_equals($body, $object->bar, $saved_object->bar, "[object] saved array should match"); - $body = assert_equals($body, $object->foo, $saved_object->foo, "[object] saved string should match"); + $body = assert_equals($body, $object->bar, $saved_object->bar, "[object] saved array should match"); + $body = assert_equals($body, $object->foo, $saved_object->foo, "[object] saved string should match"); $body = assert_equals($body, true, $actor->a_function(), 'actor can return a simple value'); @@ -134,7 +132,7 @@ function (ProxyFactory $proxyFactory, DaprClient $client, LoggerInterface $logge $app->get( '/test/state', function (StateManager $stateManager) { - $body = []; + $body = []; $state = new SimpleState(); $stateManager->save_object($state); $body = assert_equals($body, null, $state->data, 'state is empty'); @@ -149,17 +147,17 @@ function (StateManager $stateManager) { $body = assert_equals($body, 'data', $state->data, 'properly loaded saved state'); $prefix = uniqid(); - $state = new SimpleState(); + $state = new SimpleState(); $stateManager->load_object($state, $prefix); $body = assert_not_equals($body, 'data', $state->data, 'prefix should work'); $random_key = uniqid(); - $state = $stateManager->load_state('statestore', $random_key, 'hello'); - $body = assert_equals($body, 'hello', $state->value, 'single key read with default'); + $state = $stateManager->load_state('statestore', $random_key, 'hello'); + $body = assert_equals($body, 'hello', $state->value, 'single key read with default'); $stateManager->save_state('statestore', $state); $state2 = $stateManager->load_state('statestore', $random_key, 'world'); - $body = assert_equals($body, 'hello', $state2->value, 'single key write'); + $body = assert_equals($body, 'hello', $state2->value, 'single key write'); return $body; } @@ -168,19 +166,19 @@ function (StateManager $stateManager) { $app->get( '/test/state/concurrency', function (StateManager $stateManager) { - $last = new #[StateStore(STORE, StrongLastWrite::class)] class extends SimpleState { + $last = new #[StateStore(STORE, StrongLastWrite::class)] class extends SimpleState { }; $first = new #[StateStore(STORE, StrongFirstWrite::class)] class extends SimpleState { }; - $body = []; - $body = assert_equals($body, 0, $last->counter, 'initial value correct'); + $body = []; + $body = assert_equals($body, 0, $last->counter, 'initial value correct'); $stateManager->save_object($last); $stateManager->load_object($last); $stateManager->load_object($first); $body = assert_equals($body, 0, $last->counter, 'Starting from 0'); $first->counter = 1; - $last->counter = 2; + $last->counter = 2; $stateManager->save_object($last); $stateManager->load_object($last); $body = assert_equals($body, 2, $last->counter, 'last-write update succeeds'); @@ -203,10 +201,10 @@ function (StateManager $stateManager, \DI\Container $container) { $reset_state = $container->make(TState::class); $stateManager->save_object($reset_state); ($transaction = $container->make(TState::class))->begin(); - $body = []; - $body = assert_equals($body, 0, $transaction->counter, 'initial count = 0'); + $body = []; + $body = assert_equals($body, 0, $transaction->counter, 'initial count = 0'); $transaction->counter += 1; - $body = assert_equals( + $body = assert_equals( $body, 1, $transaction->counter, @@ -271,12 +269,8 @@ function () use ($one) { $app->get( '/test/pubsub', function (FactoryInterface $container) { - $publisher = $container->make(Publish::class, ['pubsub' => 'pubsub']); - /** - * @var Topic $topic - */ - $topic = $publisher->topic(topic: 'test'); - $body = []; + $topic = new Topic('pubsub', 'test', \Dapr\Client\DaprClient::clientBuilder()->build()); + $body = []; $topic->publish(['test_event']); sleep(5); @@ -298,19 +292,19 @@ function (FactoryInterface $container) { ); $return = ['simple-test' => $body]; - $body = []; + $body = []; - $event = new CloudEvent(); - $event->id = "123"; - $event->source = "http://example.com"; - $event->type = "com.example.test"; + $event = new CloudEvent(); + $event->id = "123"; + $event->source = "http://example.com"; + $event->type = "com.example.test"; $event->data_content_type = 'application/json'; - $event->subject = 'yolo'; - $event->time = new DateTime(); - $event->data = ['yolo']; + $event->subject = 'yolo'; + $event->time = new DateTime(); + $event->data = ['yolo']; $topic->publish($event); sleep(5); - $body = assert_equals( + $body = assert_equals( $body, true, file_exists('/tmp/sub-received'), @@ -319,20 +313,20 @@ function (FactoryInterface $container) { $body["Received this raw data"] = json_decode($raw_event = file_get_contents('/tmp/sub-received')); unlink('/tmp/sub-received'); //unset($event->time); - $event->topic = "test"; - $event->pubsub_name = "pubsub"; + $event->topic = "test"; + $event->pubsub_name = "pubsub"; $body["Expecting this data"] = json_decode($event->to_json()); - $received = CloudEvent::parse($raw_event); + $received = CloudEvent::parse($raw_event); unset($received->trace_id); - $body['Received this decoded data'] = json_decode($received->to_json()); - $body = assert_equals( + $body['Received this decoded data'] = json_decode($received->to_json()); + $body = assert_equals( $body, $event->to_json(), $received->to_json(), 'Event should be the same event we sent, minus the trace id.' ); $return['Testing custom cloud event'] = $body; - $body = []; + $body = []; $topic->publish( json_decode( @@ -353,7 +347,7 @@ function (FactoryInterface $container) { ) ); sleep(2); - $body = assert_equals( + $body = assert_equals( $body, true, file_exists('/tmp/sub-received'), @@ -381,14 +375,14 @@ function (FactoryInterface $container) { $app->get( '/test/invoke', function (DaprClient $client) { - $body = []; + $body = []; $result = $client->post("/invoke/dev/method/say_something", "My Message"); - $body = assert_equals($body, 200, $result->code, 'Should receive a 200 response'); + $body = assert_equals($body, 200, $result->code, 'Should receive a 200 response'); $json = '{"ok": true}'; $result = $client->post('/invoke/dev/method/test_json', $json); - $body = assert_equals($body, 200, $result->code, 'Static function should receive json string'); + $body = assert_equals($body, 200, $result->code, 'Static function should receive json string'); return $body; } @@ -408,8 +402,8 @@ function (#[FromBody] string $body) { $app->get( '/test/binding', function () { - $body = []; - $cron_file = sys_get_temp_dir().'/cron'; + $body = []; + $cron_file = sys_get_temp_dir() . '/cron'; //Binding::invoke_output('cron', 'delete'); $body = assert_equals($body, true, file_exists($cron_file), 'we should have received at least one cron'); // see https://github.com/dapr/components-contrib/issues/639 @@ -421,7 +415,7 @@ function () { return $body; } ); -$app->post('/cron', fn() => touch(sys_get_temp_dir().'/cron')); +$app->post('/cron', fn() => touch(sys_get_temp_dir() . '/cron')); $app->post( '/testsub', function ( @@ -442,20 +436,20 @@ function ( '/do_tests', function (DaprClient $client) { $test_results = [ - '/test/actors' => null, - '/test/binding' => null, - '/test/invoke' => null, - '/test/pubsub' => null, + '/test/actors' => null, + '/test/binding' => null, + '/test/invoke' => null, + '/test/pubsub' => null, '/test/state/concurrency' => null, - '/test/state' => null, + '/test/state' => null, ]; foreach (array_keys($test_results) as $suite) { - $result = $client->get('/invoke/dev/method'.$suite); - $body = []; - $body = assert_equals($body, 200, $result->code, 'test completed successfully'); + $result = $client->get('/invoke/dev/method' . $suite); + $body = []; + $body = assert_equals($body, 200, $result->code, 'test completed successfully'); $test_results[$suite] = [ - 'status' => $body, + 'status' => $body, 'results' => $result->data, ]; } diff --git a/src/lib/PubSub/Publish.php b/src/lib/PubSub/Publish.php index 91bcac6..6b3fcf9 100644 --- a/src/lib/PubSub/Publish.php +++ b/src/lib/PubSub/Publish.php @@ -5,18 +5,20 @@ use DI\DependencyException; use DI\FactoryInterface; use DI\NotFoundException; +use JetBrains\PhpStorm\Deprecated; /** * Class Publish * @package Dapr\PubSub */ +#[Deprecated(since: '1.2.0', replacement: Topic::class)] class Publish { /** * Publish constructor. * * @param string $pubsub - * @param FactoryInterface $container + * @param FactoryInterface|null $container */ public function __construct(private string $pubsub, private FactoryInterface $container) { diff --git a/src/lib/PubSub/Topic.php b/src/lib/PubSub/Topic.php index 3ff92d0..05cb80f 100644 --- a/src/lib/PubSub/Topic.php +++ b/src/lib/PubSub/Topic.php @@ -2,6 +2,7 @@ namespace Dapr\PubSub; +use Dapr\Client\DaprClient as NewClient; use Dapr\DaprClient; use Dapr\exceptions\DaprException; use Psr\Log\LoggerInterface; @@ -15,8 +16,8 @@ class Topic public function __construct( private string $pubsub, private string $topic, - private DaprClient $client, - private LoggerInterface $logger + private DaprClient|NewClient $client, + private LoggerInterface|null $logger = null ) { } @@ -29,7 +30,7 @@ public function __construct( * * @return bool Whether the event was successfully dispatched */ - public function publish(mixed $event, ?array $metadata = null, $content_type = 'application/json'): bool + public function publish(mixed $event, ?array $metadata = null, string $content_type = 'application/json'): bool { $this->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); if ($event instanceof CloudEvent) { @@ -40,14 +41,25 @@ public function publish(mixed $event, ?array $metadata = null, $content_type = ' $event = $event->to_array(); } - try { - $this->client->post("/publish/{$this->pubsub}/{$this->topic}", $event, $metadata); + if ($this->client instanceof DaprClient) { + try { + $this->client->post("/publish/{$this->pubsub}/{$this->topic}", $event, $metadata); - $this->client->extra_headers = ['Content-Type: '.$content_type]; + $this->client->extra_headers = ['Content-Type: ' . $content_type]; - return true; - } catch (DaprException) { // @codeCoverageIgnoreStart - return false; - } // @codeCoverageIgnoreEnd + return true; + } catch (DaprException) { // @codeCoverageIgnoreStart + return false; + } // @codeCoverageIgnoreEnd + } elseif ($this->client instanceof NewClient) { + try { + $this->client->publishEvent($this->pubsub, $this->topic, $event, $metadata); + return true; + } catch (DaprException) { + return false; + } + } + + return false; } } From b7888f6c47ec95e0c7e412b9fc94c64e62f48272 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 16 Jul 2021 17:21:26 +0200 Subject: [PATCH 2/5] Start refactoring tests --- src/config.php | 4 +- src/lib/Client/DaprClient.php | 14 +++++-- src/lib/Client/DaprClientBuilder.php | 3 +- src/lib/Client/DaprHttpClient.php | 11 +++-- src/lib/Client/HttpPubSubTrait.php | 17 +++++--- src/lib/PubSub/Topic.php | 15 +++++-- tests/DaprTests.php | 25 ++++++++--- tests/PublishTest.php | 63 +++++++++++++--------------- 8 files changed, 96 insertions(+), 56 deletions(-) diff --git a/src/config.php b/src/config.php index 11bf322..7742561 100644 --- a/src/config.php +++ b/src/config.php @@ -131,7 +131,9 @@ 'subscriptions', get('dapr.subscriptions') ), - Topic::class => autowire()->constructorParameter('logger', get('dapr.logger')), + Topic::class => autowire() + ->constructorParameter('logger', get('dapr.logger')) + ->constructorParameter('client', get(DaprClient::class)), Tracing::class => autowire(), Transaction::class => autowire(), TransactionalState::class => autowire()->constructorParameter('logger', get('dapr.logger')), diff --git a/src/lib/Client/DaprClient.php b/src/lib/Client/DaprClient.php index bb2fd1f..f67c4e1 100644 --- a/src/lib/Client/DaprClient.php +++ b/src/lib/Client/DaprClient.php @@ -9,6 +9,7 @@ use Dapr\Serialization\SerializationConfig; use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; /** @@ -17,8 +18,11 @@ */ abstract class DaprClient { - public function __construct(public IDeserializer $deserializer, public ISerializer $serializer) - { + public function __construct( + public IDeserializer $deserializer, + public ISerializer $serializer, + public LoggerInterface $logger + ) { } public static function clientBuilder(): DaprClientBuilder @@ -83,7 +87,8 @@ abstract public function publishEvent( string $pubsubName, string $topicName, mixed $data, - array $metadata = [] + array $metadata = [], + string $contentType = 'application/json' ): void; /** @@ -98,7 +103,8 @@ abstract public function publishEventAsync( string $pubsubName, string $topicName, mixed $data, - array $metadata = [] + array $metadata = [], + string $contentType = 'application/json' ): PromiseInterface; /** diff --git a/src/lib/Client/DaprClientBuilder.php b/src/lib/Client/DaprClientBuilder.php index 583d23f..f67dfe2 100644 --- a/src/lib/Client/DaprClientBuilder.php +++ b/src/lib/Client/DaprClientBuilder.php @@ -50,7 +50,8 @@ public function build(): DaprClient return new DaprHttpClient( $this->defaultHttpHost, new Deserializer($this->deserializationConfig, $this->logger), - new Serializer($this->serializationConfig, $this->logger) + new Serializer($this->serializationConfig, $this->logger), + $this->logger ); } } diff --git a/src/lib/Client/DaprHttpClient.php b/src/lib/Client/DaprHttpClient.php index 84fa023..5472c1c 100644 --- a/src/lib/Client/DaprHttpClient.php +++ b/src/lib/Client/DaprHttpClient.php @@ -5,6 +5,7 @@ use Dapr\Deserialization\IDeserializer; use Dapr\Serialization\ISerializer; use GuzzleHttp\Client; +use Psr\Log\LoggerInterface; /** * Class DaprHttpClient @@ -20,9 +21,13 @@ class DaprHttpClient extends DaprClient private Client $httpClient; - public function __construct(private string $baseHttpUri, IDeserializer $deserializer, ISerializer $serializer) - { - parent::__construct($deserializer, $serializer); + public function __construct( + private string $baseHttpUri, + IDeserializer $deserializer, + ISerializer $serializer, + LoggerInterface $logger + ) { + parent::__construct($deserializer, $serializer, $logger); if (str_ends_with($this->baseHttpUri, '/')) { $this->baseHttpUri = rtrim($this->baseHttpUri, '/'); } diff --git a/src/lib/Client/HttpPubSubTrait.php b/src/lib/Client/HttpPubSubTrait.php index 0638059..0b661d6 100644 --- a/src/lib/Client/HttpPubSubTrait.php +++ b/src/lib/Client/HttpPubSubTrait.php @@ -23,20 +23,27 @@ trait HttpPubSubTrait /** * @throws DaprException */ - public function publishEvent(string $pubsubName, string $topicName, mixed $data, array $metadata = []): void - { - $this->publishEventAsync($pubsubName, $topicName, $data, $metadata)->wait(); + public function publishEvent( + string $pubsubName, + string $topicName, + mixed $data, + array $metadata = [], + string $contentType = 'application/json' + ): void { + $this->publishEventAsync($pubsubName, $topicName, $data, $metadata, $contentType)->wait(); } public function publishEventAsync( string $pubsubName, string $topicName, mixed $data, - array $metadata = [] + array $metadata = [], + string $contentType = 'application/json' ): PromiseInterface { $options = [ 'query' => $metadata, - 'body' => $this->serializer->as_json($data) + 'body' => $this->serializer->as_json($data), + 'header' => [] ]; $pubsubName = rawurlencode($pubsubName); $topicName = rawurlencode($topicName); diff --git a/src/lib/PubSub/Topic.php b/src/lib/PubSub/Topic.php index 05cb80f..a422e79 100644 --- a/src/lib/PubSub/Topic.php +++ b/src/lib/PubSub/Topic.php @@ -5,6 +5,7 @@ use Dapr\Client\DaprClient as NewClient; use Dapr\DaprClient; use Dapr\exceptions\DaprException; +use JetBrains\PhpStorm\Deprecated; use Psr\Log\LoggerInterface; /** @@ -17,7 +18,7 @@ public function __construct( private string $pubsub, private string $topic, private DaprClient|NewClient $client, - private LoggerInterface|null $logger = null + #[Deprecated(since: '1.2.0')] private LoggerInterface|null $logger = null ) { } @@ -32,7 +33,11 @@ public function __construct( */ public function publish(mixed $event, ?array $metadata = null, string $content_type = 'application/json'): bool { - $this->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); + if ($this->logger !== null) { + $this->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); + } elseif ($this->client instanceof NewClient) { + $this->client->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); + } if ($event instanceof CloudEvent) { $this->client->extra_headers = [ 'Content-Type: application/cloudevents+json', @@ -51,9 +56,11 @@ public function publish(mixed $event, ?array $metadata = null, string $content_t } catch (DaprException) { // @codeCoverageIgnoreStart return false; } // @codeCoverageIgnoreEnd - } elseif ($this->client instanceof NewClient) { + } + + if ($this->client instanceof NewClient) { try { - $this->client->publishEvent($this->pubsub, $this->topic, $event, $metadata); + $this->client->publishEvent($this->pubsub, $this->topic, $event, $metadata ?? [], $content_type); return true; } catch (DaprException) { return false; diff --git a/tests/DaprTests.php b/tests/DaprTests.php index 46b9e48..8375d86 100644 --- a/tests/DaprTests.php +++ b/tests/DaprTests.php @@ -1,14 +1,16 @@ addDefinitions(__DIR__.'/../src/config.php'); - $builder->addDefinitions(['dapr.log.level' => LogLevel::EMERGENCY, DaprClient::class => autowire(TestClient::class)]); + $builder->addDefinitions(__DIR__ . '/../src/config.php'); + $builder->addDefinitions( + ['dapr.log.level' => LogLevel::EMERGENCY, DaprClient::class => autowire(TestClient::class)] + ); $builder->addDefinitions($config); $this->container = $builder->build(); } @@ -63,6 +68,16 @@ protected function get_client(): TestClient return $this->container->get(DaprClient::class); } + protected function get_new_client(): NewClient|MockObject + { + $client = $this->createMock(NewClient::class); + $client->logger = new \Psr\Log\NullLogger(); + $client->deserializer = $this->container->get(\Dapr\Deserialization\IDeserializer::class); + $client->serializer = $this->container->get(\Dapr\Serialization\ISerializer::class); + + return $client; + } + protected function deserialize(string $json) { return json_decode($json, true); diff --git a/tests/PublishTest.php b/tests/PublishTest.php index c01d952..6aed38d 100644 --- a/tests/PublishTest.php +++ b/tests/PublishTest.php @@ -3,11 +3,15 @@ use Dapr\DaprClient; use Dapr\PubSub\CloudEvent; use Dapr\PubSub\Publish; +use Dapr\PubSub\Topic; use DI\DependencyException; use DI\NotFoundException; -require_once __DIR__.'/DaprTests.php'; +require_once __DIR__ . '/DaprTests.php'; +/** + * Class PublishTest + */ class PublishTest extends DaprTests { /** @@ -16,25 +20,18 @@ class PublishTest extends DaprTests */ public function testSimplePublish() { - $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); - $this->get_client()->register_post( - '/publish/pubsub/topic', - 200, - null, - [ - 'my' => 'event', - ] - ); - $publisher->topic('topic')->publish(['my' => 'event']); + $client = $this->get_new_client(); + $client->expects($this->once())->method('publishEvent')->with(); + $topic = new Topic('pubsub', 'topic', $client); + $topic->publish(['my' => 'event']); } public function testBinaryPublish() { - $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); - $this->get_client()->register_post('/publish/pubsub/topic', 200, null, 'data'); - $publisher->topic('topic')->publish('data', content_type: 'application/octet-stream'); - $client = $this->container->get(DaprClient::class); - $this->assertSame(['Content-Type: application/octet-stream'], $client->extra_headers); + $client = $this->get_new_client(); + $client->expects($this->once())->method('publishEvent'); + $topic = new Topic('pubsub', 'test', $client); + $topic->publish('data', content_type: 'application/octet-stream'); } /** @@ -43,28 +40,28 @@ public function testBinaryPublish() */ public function testCloudEventPublish() { - $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); - $event = new CloudEvent(); - $event->data = ['my' => 'event']; - $event->type = 'type'; - $event->subject = 'subject'; - $event->id = 'id'; + $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); + $event = new CloudEvent(); + $event->data = ['my' => 'event']; + $event->type = 'type'; + $event->subject = 'subject'; + $event->id = 'id'; $event->data_content_type = 'application/json'; - $event->source = 'source'; - $event->time = new DateTime('2020-12-12T20:47:00+00:00Z'); + $event->source = 'source'; + $event->time = new DateTime('2020-12-12T20:47:00+00:00Z'); $this->get_client()->register_post( '/publish/pubsub/topic', 200, null, [ - 'id' => 'id', - 'source' => 'source', - 'specversion' => '1.0', - 'type' => 'type', + 'id' => 'id', + 'source' => 'source', + 'specversion' => '1.0', + 'type' => 'type', 'datacontenttype' => 'application/json', - 'subject' => 'subject', - 'time' => '2020-12-12T20:47:00+00:00Z', - 'data' => [ + 'subject' => 'subject', + 'time' => '2020-12-12T20:47:00+00:00Z', + 'data' => [ 'my' => 'event', ], ] @@ -89,7 +86,7 @@ public function testParsingCloudEvent() "data" : "User1user2hi" } JSON; - $event = CloudEvent::parse($eventjson); + $event = CloudEvent::parse($eventjson); $this->assertTrue($event->validate()); $this->assertSame('https://example.com/message', $event->source); } @@ -109,7 +106,7 @@ public function testParsingBinaryEvent() "data_base64": "ZGF0YQ==" } JSON; - $event = CloudEvent::parse($eventjson); + $event = CloudEvent::parse($eventjson); $this->assertTrue($event->validate()); $this->assertSame('data', $event->data); } From 8c8e457899a673fd003f1a02ed15e1ab8ecc07f4 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 16 Jul 2021 17:29:24 +0200 Subject: [PATCH 3/5] Update tests for new client --- src/lib/PubSub/Topic.php | 1 + tests/PublishTest.php | 42 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/lib/PubSub/Topic.php b/src/lib/PubSub/Topic.php index a422e79..eb325b3 100644 --- a/src/lib/PubSub/Topic.php +++ b/src/lib/PubSub/Topic.php @@ -39,6 +39,7 @@ public function publish(mixed $event, ?array $metadata = null, string $content_t $this->client->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); } if ($event instanceof CloudEvent) { + $content_type = 'application/cloudevents+json'; $this->client->extra_headers = [ 'Content-Type: application/cloudevents+json', ]; diff --git a/tests/PublishTest.php b/tests/PublishTest.php index 6aed38d..91ba305 100644 --- a/tests/PublishTest.php +++ b/tests/PublishTest.php @@ -1,6 +1,5 @@ get_new_client(); - $client->expects($this->once())->method('publishEvent')->with(); + $client->expects($this->once())->method('publishEvent')->with( + $this->equalTo('pubsub'), + $this->equalTo('topic'), + $this->equalTo(['my' => 'event']), + $this->equalTo([]), + $this->equalTo('application/json') + ); $topic = new Topic('pubsub', 'topic', $client); $topic->publish(['my' => 'event']); } @@ -29,7 +34,13 @@ public function testSimplePublish() public function testBinaryPublish() { $client = $this->get_new_client(); - $client->expects($this->once())->method('publishEvent'); + $client->expects($this->once())->method('publishEvent')->with( + $this->equalTo('pubsub'), + $this->equalTo('test'), + $this->equalTo('data'), + $this->equalTo([]), + $this->equalTo('application/octet-stream') + ); $topic = new Topic('pubsub', 'test', $client); $topic->publish('data', content_type: 'application/octet-stream'); } @@ -40,7 +51,6 @@ public function testBinaryPublish() */ public function testCloudEventPublish() { - $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); $event = new CloudEvent(); $event->data = ['my' => 'event']; $event->type = 'type'; @@ -49,6 +59,30 @@ public function testCloudEventPublish() $event->data_content_type = 'application/json'; $event->source = 'source'; $event->time = new DateTime('2020-12-12T20:47:00+00:00Z'); + + $client = $this->get_new_client(); + $client->expects($this->once())->method('publishEvent')->with( + $this->equalTo('pubsub'), + $this->equalTo('test'), + $this->equalTo([ + 'id' => 'id', + 'source' => 'source', + 'specversion' => '1.0', + 'type' => 'type', + 'datacontenttype' => 'application/json', + 'subject' => 'subject', + 'time' => '2020-12-12T20:47:00+00:00Z', + 'data' => [ + 'my' => 'event', + ], + ]), + $this->equalTo([]), + $this->equalTo('application/cloudevents+json') + ); + $topic = new Topic('pubsub', 'test', $client); + $topic->publish($event); + + $publisher = $this->container->make(Publish::class, ['pubsub' => 'pubsub']); $this->get_client()->register_post( '/publish/pubsub/topic', 200, From ba2e00130c15a7853b420f43f631aaf483e48a84 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 16 Jul 2021 17:33:52 +0200 Subject: [PATCH 4/5] Encode metadata --- src/lib/Client/HttpPubSubTrait.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/Client/HttpPubSubTrait.php b/src/lib/Client/HttpPubSubTrait.php index 0b661d6..6835a5e 100644 --- a/src/lib/Client/HttpPubSubTrait.php +++ b/src/lib/Client/HttpPubSubTrait.php @@ -41,7 +41,9 @@ public function publishEventAsync( string $contentType = 'application/json' ): PromiseInterface { $options = [ - 'query' => $metadata, + 'query' => array_merge( + ...array_map(fn($key, $value) => ["metadata.$key" => $value], array_keys($metadata), $metadata) + ), 'body' => $this->serializer->as_json($data), 'header' => [] ]; From ab96eb67a68902d60e54317bf9cb17d5f1872624 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 16 Jul 2021 19:27:39 +0200 Subject: [PATCH 5/5] Do deeper tests --- src/lib/Client/HttpPubSubTrait.php | 6 +- src/lib/Client/PromiseHandlingTrait.php | 4 +- tests/DaprTests.php | 59 +++++++++++++++++++ tests/Mocks/MockedHttpClientContainer.php | 19 ++++++ tests/PublishTest.php | 72 ++++++++++++----------- 5 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 tests/Mocks/MockedHttpClientContainer.php diff --git a/src/lib/Client/HttpPubSubTrait.php b/src/lib/Client/HttpPubSubTrait.php index 6835a5e..040d4e6 100644 --- a/src/lib/Client/HttpPubSubTrait.php +++ b/src/lib/Client/HttpPubSubTrait.php @@ -30,7 +30,7 @@ public function publishEvent( array $metadata = [], string $contentType = 'application/json' ): void { - $this->publishEventAsync($pubsubName, $topicName, $data, $metadata, $contentType)->wait(); + $this->publishEventAsync($pubsubName, $topicName, $data, $metadata, $contentType)->wait(true); } public function publishEventAsync( @@ -45,7 +45,9 @@ public function publishEventAsync( ...array_map(fn($key, $value) => ["metadata.$key" => $value], array_keys($metadata), $metadata) ), 'body' => $this->serializer->as_json($data), - 'header' => [] + 'headers' => [ + 'Content-Type' => $contentType, + ] ]; $pubsubName = rawurlencode($pubsubName); $topicName = rawurlencode($topicName); diff --git a/src/lib/Client/PromiseHandlingTrait.php b/src/lib/Client/PromiseHandlingTrait.php index 352adc7..9fef482 100644 --- a/src/lib/Client/PromiseHandlingTrait.php +++ b/src/lib/Client/PromiseHandlingTrait.php @@ -26,14 +26,14 @@ private function handlePromise( } if (empty($errorTransformer)) { $errorTransformer = fn(\Throwable $exception) => match ($exception::class) { - ServerException::class, ClientException::class => new DaprException( + ServerException::class, ClientException::class => throw new DaprException( $exception->hasResponse() ? $exception->getResponse()->getBody()->getContents() : $exception->getMessage(), $exception->getCode(), $exception ), - default => $exception + default => throw $exception }; } return $closure->then( diff --git a/tests/DaprTests.php b/tests/DaprTests.php index 8375d86..9351d87 100644 --- a/tests/DaprTests.php +++ b/tests/DaprTests.php @@ -1,15 +1,20 @@ container->get(DaprClient::class); } + public function assertRequestMethod(string $expectedMethod, \GuzzleHttp\Psr7\Request $request): void + { + $this->assertSame($expectedMethod, $request->getMethod()); + } + + public function assertRequestUri(string $expectedUri, \GuzzleHttp\Psr7\Request $request): void + { + $this->assertSame($expectedUri, $request->getUri()->getPath()); + } + + public function assertRequestQueryString(string $expectedQuery, \GuzzleHttp\Psr7\Request $request): void + { + $this->assertSame($expectedQuery, $request->getUri()->getQuery()); + } + + public function assertRequestHasHeaders(array $expectedHeaders, \GuzzleHttp\Psr7\Request $request): void + { + foreach ($expectedHeaders as $name => $header) { + $this->assertTrue($request->hasHeader($name), 'Request does not have ' . $name); + $actual = $request->getHeader($name); + $this->assertTrue(in_array($header, $actual), "Request missing '$name: $header', found '$name: {$actual[0]}'"); + } + } + + public function assertRequestBody(string $body, \GuzzleHttp\Psr7\Request $request): void + { + $this->assertSame($body, $request->getBody()->getContents()); + } + protected function get_new_client(): NewClient|MockObject { $client = $this->createMock(NewClient::class); @@ -78,6 +112,31 @@ protected function get_new_client(): NewClient|MockObject return $client; } + protected function get_new_client_with_http(\GuzzleHttp\Client|MockObject $mock): NewClient + { + $reflection = new ReflectionClass(\Dapr\Client\DaprHttpClient::class); + $property = $reflection->getProperty('httpClient'); + $property->setAccessible(true); + + $client = \Dapr\Client\DaprClient::clientBuilder()->build(); + $property->setValue($client, $mock); + return $client; + } + + protected function get_http_client_stack( + array $responseQueue = [], + array|null &$history = null + ): MockedHttpClientContainer { + $container = new MockedHttpClientContainer(); + $container->mock = new MockHandler($responseQueue); + $history = Middleware::history($container->history); + $container->handlerStack = HandlerStack::create($container->mock); + $container->handlerStack->push($history); + $container->client = new \GuzzleHttp\Client(['handler' => $container->handlerStack]); + $history = &$container->history; + return $container; + } + protected function deserialize(string $json) { return json_decode($json, true); diff --git a/tests/Mocks/MockedHttpClientContainer.php b/tests/Mocks/MockedHttpClientContainer.php new file mode 100644 index 0000000..7867f68 --- /dev/null +++ b/tests/Mocks/MockedHttpClientContainer.php @@ -0,0 +1,19 @@ +get_new_client(); - $client->expects($this->once())->method('publishEvent')->with( - $this->equalTo('pubsub'), - $this->equalTo('topic'), - $this->equalTo(['my' => 'event']), - $this->equalTo([]), - $this->equalTo('application/json') + $container = $this->get_http_client_stack( + [ + new \GuzzleHttp\Psr7\Response(204) + ] ); + $client = $this->get_new_client_with_http($container->client); $topic = new Topic('pubsub', 'topic', $client); - $topic->publish(['my' => 'event']); + $topic->publish(['my' => 'event'], ['test' => 'meta']); + + $request = $container->history[0]['request']; + $this->assertRequestMethod('POST', $request); + $this->assertRequestUri('/v1.0/publish/pubsub/topic', $request); + $this->assertRequestQueryString('metadata.test=meta', $request); + $this->assertRequestHasHeaders(['Content-Type' => 'application/json'], $request); + $this->assertRequestBody(json_encode(['my' => 'event']), $request); } public function testBinaryPublish() { - $client = $this->get_new_client(); - $client->expects($this->once())->method('publishEvent')->with( - $this->equalTo('pubsub'), - $this->equalTo('test'), - $this->equalTo('data'), - $this->equalTo([]), - $this->equalTo('application/octet-stream') + $container = $this->get_http_client_stack( + [ + new \GuzzleHttp\Psr7\Response(204), + ] ); + $client = $this->get_new_client_with_http($container->client); $topic = new Topic('pubsub', 'test', $client); $topic->publish('data', content_type: 'application/octet-stream'); + + $request = $container->history[0]['request']; + $this->assertRequestMethod('POST', $request); + $this->assertRequestUri('/v1.0/publish/pubsub/test', $request); + $this->assertRequestQueryString('', $request); + $this->assertRequestHasHeaders(['Content-Type' => 'application/octet-stream'], $request); + $this->assertRequestBody('"data"', $request); } /** @@ -51,6 +61,9 @@ public function testBinaryPublish() */ public function testCloudEventPublish() { + $container = $this->get_http_client_stack([new \GuzzleHttp\Psr7\Response(204)]); + $client = $this->get_new_client_with_http($container->client); + $event = new CloudEvent(); $event->data = ['my' => 'event']; $event->type = 'type'; @@ -60,25 +73,6 @@ public function testCloudEventPublish() $event->source = 'source'; $event->time = new DateTime('2020-12-12T20:47:00+00:00Z'); - $client = $this->get_new_client(); - $client->expects($this->once())->method('publishEvent')->with( - $this->equalTo('pubsub'), - $this->equalTo('test'), - $this->equalTo([ - 'id' => 'id', - 'source' => 'source', - 'specversion' => '1.0', - 'type' => 'type', - 'datacontenttype' => 'application/json', - 'subject' => 'subject', - 'time' => '2020-12-12T20:47:00+00:00Z', - 'data' => [ - 'my' => 'event', - ], - ]), - $this->equalTo([]), - $this->equalTo('application/cloudevents+json') - ); $topic = new Topic('pubsub', 'test', $client); $topic->publish($event); @@ -101,6 +95,16 @@ public function testCloudEventPublish() ] ); $publisher->topic('topic')->publish($event); + + $request = $container->history[0]['request']; + $this->assertRequestMethod('POST', $request); + $this->assertRequestUri('/v1.0/publish/pubsub/test', $request); + $this->assertRequestQueryString('', $request); + $this->assertRequestHasHeaders(['Content-Type' => 'application/cloudevents+json'], $request); + $this->assertRequestBody( + '{"id":"id","source":"source","specversion":"1.0","type":"type","datacontenttype":"application\/json","subject":"subject","time":"2020-12-12T20:47:00+00:00Z","data":{"my":"event"}}', + $request + ); } /**