diff --git a/composer.json b/composer.json index d318b2c..9655419 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "sort-packages": true }, "scripts": { - "analyse": "vendor/bin/phpstan analyse -c phpstan.neon", + "analyse": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=256M", "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes src", "test": "vendor/bin/phpunit", "test-coverage": "vendor/bin/phpunit --coverage-html coverage" diff --git a/src/Api.php b/src/Api.php index ebe4197..ae79395 100644 --- a/src/Api.php +++ b/src/Api.php @@ -68,10 +68,12 @@ class Api ) { if ($client === null) { if (!class_exists(\GuzzleHttp\Client::class) || !class_exists(\GuzzleHttp\Psr7\HttpFactory::class)) { + // @codeCoverageIgnoreStart throw new LogicException( 'No client was provided and "guzzlehttp/guzzle" is not found. ' . 'Please run "composer require guzzlehttp/guzzle" or create and pass your own implementation of ClientApiInterface.' ); + // @codeCoverageIgnoreEnd } $guzzle = new \GuzzleHttp\Client(); diff --git a/src/ModelFactory.php b/src/ModelFactory.php index b6b7b86..a102795 100644 --- a/src/ModelFactory.php +++ b/src/ModelFactory.php @@ -12,8 +12,12 @@ use BushlanovDev\MaxMessengerBot\Models\Result; use BushlanovDev\MaxMessengerBot\Models\Subscription; use BushlanovDev\MaxMessengerBot\Models\UpdateList; use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate; +use BushlanovDev\MaxMessengerBot\Models\Updates\BotAddedToChatUpdate; use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate; +use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCallbackUpdate; use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate; +use BushlanovDev\MaxMessengerBot\Models\Updates\MessageEditedUpdate; +use BushlanovDev\MaxMessengerBot\Models\Updates\MessageRemovedUpdate; use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint; use LogicException; use ReflectionException; @@ -154,6 +158,10 @@ class ModelFactory { return match (UpdateType::tryFrom($data['update_type'] ?? '')) { UpdateType::MessageCreated => MessageCreatedUpdate::fromArray($data), + UpdateType::MessageCallback => MessageCallbackUpdate::fromArray($data), + UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data), + UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data), + UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data), UpdateType::BotStarted => BotStartedUpdate::fromArray($data), default => throw new LogicException( 'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none') diff --git a/src/Models/Callback.php b/src/Models/Callback.php new file mode 100644 index 0000000..82e8ba2 --- /dev/null +++ b/src/Models/Callback.php @@ -0,0 +1,25 @@ +addHandler(UpdateType::MessageCreated, $handler); } + /** + * A convenient alias for addHandler(UpdateType::MessageCallback, $handler). + * + * @param callable(Models\Updates\MessageCallbackUpdate, Api): void $handler + * + * @return $this + * @codeCoverageIgnore + */ + public function onMessageCallback(callable $handler): self + { + return $this->addHandler(UpdateType::MessageCallback, $handler); + } + + /** + * A convenient alias for addHandler(UpdateType::MessageEdited, $handler). + * + * @param callable(Models\Updates\MessageEditedUpdate, Api): void $handler + * + * @return $this + * @codeCoverageIgnore + */ + public function onMessageEdited(callable $handler): self + { + return $this->addHandler(UpdateType::MessageEdited, $handler); + } + + /** + * A convenient alias for addHandler(UpdateType::MessageRemoved, $handler). + * + * @param callable(Models\Updates\MessageRemovedUpdate, Api): void $handler + * + * @return $this + * @codeCoverageIgnore + */ + public function onMessageRemoved(callable $handler): self + { + return $this->addHandler(UpdateType::MessageRemoved, $handler); + } + /** + * A convenient alias for addHandler(UpdateType::BotAdded, $handler). + * + * @param callable(Models\Updates\BotAddedToChatUpdate, Api): void $handler + * + * @return $this + * @codeCoverageIgnore + */ + public function onBotAdded(callable $handler): self + { + return $this->addHandler(UpdateType::BotAdded, $handler); + } + /** * A convenient alias for addHandler(UpdateType::BotStarted, $handler). * @@ -92,10 +143,12 @@ final class WebhookHandler { if ($request === null) { if (!class_exists(\GuzzleHttp\Psr7\ServerRequest::class)) { + // @codeCoverageIgnoreStart throw new \LogicException( 'No ServerRequest was provided and "guzzlehttp/psr7" is not found. ' . 'Please run "composer require guzzlehttp/psr7" or create and pass your own PSR-7 request object.', ); + // @codeCoverageIgnoreEnd } $request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals(); } diff --git a/tests/Models/CallbackTest.php b/tests/Models/CallbackTest.php new file mode 100644 index 0000000..cd13619 --- /dev/null +++ b/tests/Models/CallbackTest.php @@ -0,0 +1,48 @@ + 1678886400, + 'callback_id' => 'cb.12345.abc', + 'payload' => 'button_1_pressed', + 'user' => [ + 'user_id' => 101, + 'first_name' => 'Jane', + 'is_bot' => false, + 'last_activity_time' => 1678886000, + 'last_name' => null, + 'username' => null, + 'description' => null, + 'avatar_url' => null, + 'full_avatar_url' => null, + ], + ]; + + $callback = Callback::fromArray($data); + + $this->assertInstanceOf(Callback::class, $callback); + $this->assertSame(1678886400, $callback->timestamp); + $this->assertSame('cb.12345.abc', $callback->callbackId); + $this->assertSame('button_1_pressed', $callback->payload); + $this->assertInstanceOf(User::class, $callback->user); + $this->assertSame(101, $callback->user->userId); + $this->assertEquals($data, $callback->toArray()); + } +} diff --git a/tests/Models/Updates/BotAddedToChatUpdateTest.php b/tests/Models/Updates/BotAddedToChatUpdateTest.php new file mode 100644 index 0000000..8e61c7a --- /dev/null +++ b/tests/Models/Updates/BotAddedToChatUpdateTest.php @@ -0,0 +1,74 @@ + 'bot_added', + 'timestamp' => 1679000000, + 'chat_id' => 987654321, + 'user' => [ + 'user_id' => 101, + 'first_name' => 'Admin', + 'is_bot' => false, + 'last_activity_time' => 1678000000, + 'last_name' => null, + 'username' => null, + 'description' => null, + 'avatar_url' => null, + 'full_avatar_url' => null, + ], + 'is_channel' => false, + ]; + + $update = BotAddedToChatUpdate::fromArray($data); + + $this->assertInstanceOf(BotAddedToChatUpdate::class, $update); + $this->assertSame(UpdateType::BotAdded, $update->updateType); + $this->assertSame(1679000000, $update->timestamp); + $this->assertSame(987654321, $update->chatId); + $this->assertFalse($update->isChannel); + $this->assertInstanceOf(User::class, $update->user); + $this->assertSame(101, $update->user->userId); + $this->assertEquals($data, $update->toArray()); + } + + #[Test] + public function canBeCreatedForChannel(): void + { + $data = [ + 'update_type' => 'bot_added', + 'timestamp' => 1679000001, + 'chat_id' => 111222333, + 'user' => [ + 'user_id' => 102, + 'first_name' => 'ChannelCreator', + 'is_bot' => false, + 'last_activity_time' => 1678000001, + ], + 'is_channel' => true, + ]; + + $update = BotAddedToChatUpdate::fromArray($data); + + $this->assertInstanceOf(BotAddedToChatUpdate::class, $update); + $this->assertTrue($update->isChannel); + $this->assertSame(111222333, $update->chatId); + } +} diff --git a/tests/Models/Updates/MessageCallbackUpdateTest.php b/tests/Models/Updates/MessageCallbackUpdateTest.php new file mode 100644 index 0000000..0b7fb8f --- /dev/null +++ b/tests/Models/Updates/MessageCallbackUpdateTest.php @@ -0,0 +1,86 @@ + 'message_callback', + 'timestamp' => 1678886400, + 'callback' => [ + 'timestamp' => 1678886400, + 'callback_id' => 'cb.12345.abc', + 'payload' => 'button_1_pressed', + 'user' => [ + 'user_id' => 101, 'first_name' => 'Jane', + 'is_bot' => false, 'last_activity_time' => 1678886000, + ], + ], + 'message' => [ + 'timestamp' => 1678886300, + 'body' => ['mid' => 'mid.123', 'seq' => 1, 'text' => 'Press a button'], + 'recipient' => ['chat_type' => 'dialog', 'user_id' => 101], + ], + 'user_locale' => 'en-US', + ]; + + $update = MessageCallbackUpdate::fromArray($data); + + $this->assertInstanceOf(MessageCallbackUpdate::class, $update); + $this->assertSame(UpdateType::MessageCallback, $update->updateType); + $this->assertInstanceOf(Callback::class, $update->callback); + $this->assertInstanceOf(Message::class, $update->message); + $this->assertSame('en-US', $update->userLocale); + $this->assertSame('button_1_pressed', $update->callback->payload); + $this->assertSame('mid.123', $update->message->body->mid); + } + + #[Test] + public function canBeCreatedWithNullableFieldsAsNull(): void + { + $data = [ + 'update_type' => 'message_callback', + 'timestamp' => 1678886400, + 'callback' => [ + 'timestamp' => 1678886400, + 'callback_id' => 'cb.12345.abc', + 'payload' => 'button_1_pressed', + 'user' => [ + 'user_id' => 101, 'first_name' => 'Jane', + 'is_bot' => false, 'last_activity_time' => 1678886000, + ], + ], + 'message' => null, + 'user_locale' => null, + ]; + + $update = MessageCallbackUpdate::fromArray($data); + + $this->assertInstanceOf(MessageCallbackUpdate::class, $update); + $this->assertNull($update->message); + $this->assertNull($update->userLocale); + } +} diff --git a/tests/Models/Updates/MessageEditedUpdateTest.php b/tests/Models/Updates/MessageEditedUpdateTest.php new file mode 100644 index 0000000..97ccc8d --- /dev/null +++ b/tests/Models/Updates/MessageEditedUpdateTest.php @@ -0,0 +1,57 @@ + 'message_edited', + 'timestamp' => 1678887000, + 'message' => [ + 'timestamp' => 1678886300, + 'body' => [ + 'mid' => 'mid.123.xyz', + 'seq' => 1, + 'text' => 'This is the new, edited text!', + ], + 'recipient' => ['chat_type' => 'dialog', 'user_id' => 101], + 'sender' => [ + 'user_id' => 101, + 'first_name' => 'Jane', + 'is_bot' => false, + 'last_activity_time' => 1678886000, + ], + ], + ]; + + $update = MessageEditedUpdate::fromArray($data); + + $this->assertInstanceOf(MessageEditedUpdate::class, $update); + $this->assertSame(UpdateType::MessageEdited, $update->updateType); + $this->assertSame(1678887000, $update->timestamp); + $this->assertInstanceOf(Message::class, $update->message); + $this->assertSame('This is the new, edited text!', $update->message->body->text); + $this->assertSame('mid.123.xyz', $update->message->body->mid); + } +} diff --git a/tests/Models/Updates/MessageRemovedUpdateTest.php b/tests/Models/Updates/MessageRemovedUpdateTest.php new file mode 100644 index 0000000..80e1116 --- /dev/null +++ b/tests/Models/Updates/MessageRemovedUpdateTest.php @@ -0,0 +1,37 @@ + 'message_removed', + 'timestamp' => 1678888000, + 'message_id' => 'mid.was.here.now.gone', + 'chat_id' => 123456789, + 'user_id' => 98765, + ]; + + $update = MessageRemovedUpdate::fromArray($data); + + $this->assertInstanceOf(MessageRemovedUpdate::class, $update); + $this->assertSame(UpdateType::MessageRemoved, $update->updateType); + $this->assertSame(1678888000, $update->timestamp); + $this->assertSame('mid.was.here.now.gone', $update->messageId); + $this->assertSame(123456789, $update->chatId); + $this->assertSame(98765, $update->userId); + $this->assertEquals($data, $update->toArray()); + } +}