diff --git a/src/ModelFactory.php b/src/ModelFactory.php index 9bc3fdb..9a9c1cc 100644 --- a/src/ModelFactory.php +++ b/src/ModelFactory.php @@ -5,11 +5,32 @@ declare(strict_types=1); namespace BushlanovDev\MaxMessengerBot; use BushlanovDev\MaxMessengerBot\Enums\AttachmentType; +use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType; use BushlanovDev\MaxMessengerBot\Enums\MarkupType; +use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType; use BushlanovDev\MaxMessengerBot\Enums\UpdateType; use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\AudioAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ChatButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestContactButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestGeoLocationButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendContactButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendGeoLocationButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendMessageButton; +use BushlanovDev\MaxMessengerBot\Models\Attachments\ContactAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\DataAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\FileAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\LocationAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\ReplyKeyboardAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\StickerAttachment; +use BushlanovDev\MaxMessengerBot\Models\Attachments\VideoAttachment; use BushlanovDev\MaxMessengerBot\Models\BotInfo; use BushlanovDev\MaxMessengerBot\Models\Chat; use BushlanovDev\MaxMessengerBot\Models\ChatList; @@ -142,15 +163,80 @@ class ModelFactory */ public function createAttachment(array $data): AbstractAttachment { - return match (AttachmentType::tryFrom($data['type'] ?? '')) { + $attachmentType = AttachmentType::tryFrom($data['type'] ?? ''); + if ($attachmentType === AttachmentType::ReplyKeyboard + && isset($data['buttons']) && is_array($data['buttons'])) { + $data['buttons'] = array_map( + fn($rowOfButtons) => array_map([$this, 'createReplyButton'], $rowOfButtons), + $data['buttons'], + ); + } + + if ($attachmentType === AttachmentType::InlineKeyboard + && isset($data['payload']['buttons']) && is_array($data['payload']['buttons'])) { + $data['payload']['buttons'] = array_map( + fn($rowOfButtons) => array_map([$this, 'createInlineButton'], $rowOfButtons), + $data['payload']['buttons'] + ); + } + + return match ($attachmentType) { AttachmentType::Data => DataAttachment::fromArray($data), AttachmentType::Share => ShareAttachment::fromArray($data), + AttachmentType::Image => PhotoAttachment::fromArray($data), + AttachmentType::Video => VideoAttachment::fromArray($data), + AttachmentType::Audio => AudioAttachment::fromArray($data), + AttachmentType::File => FileAttachment::fromArray($data), + AttachmentType::Sticker => StickerAttachment::fromArray($data), + AttachmentType::Contact => ContactAttachment::fromArray($data), + AttachmentType::InlineKeyboard => InlineKeyboardAttachment::fromArray($data), + AttachmentType::ReplyKeyboard => ReplyKeyboardAttachment::fromArray($data), + AttachmentType::Location => LocationAttachment::fromArray($data), + default => throw new LogicException("Unknown or unsupported attachment type: " . ($data['type'] ?? 'none')), + }; + } + + /** + * Creates a specific ReplyButton model based on the 'type' field. + * + * @param array $data + * + * @return AbstractReplyButton + * @throws ReflectionException + * @throws LogicException + */ + public function createReplyButton(array $data): AbstractReplyButton + { + return match (ReplyButtonType::tryFrom($data['type'] ?? '')) { + ReplyButtonType::Message => SendMessageButton::fromArray($data), + ReplyButtonType::UserContact => SendContactButton::fromArray($data), + ReplyButtonType::UserGeoLocation => SendGeoLocationButton::fromArray($data), default => throw new LogicException( - 'Unknown or unsupported Attachment type: ' . ($data['type'] ?? 'none') + 'Unknown or unsupported reply button type: ' . ($data['type'] ?? 'none') ), }; } + /** + * Creates a specific InlineButton model based on the 'type' field. + * + * @param array $data + * @return AbstractInlineButton + * @throws ReflectionException + * @throws LogicException + */ + public function createInlineButton(array $data): AbstractInlineButton + { + return match (InlineButtonType::tryFrom($data['type'] ?? '')) { + InlineButtonType::Callback => CallbackButton::fromArray($data), + InlineButtonType::Link => LinkButton::fromArray($data), + InlineButtonType::RequestContact => RequestContactButton::fromArray($data), + InlineButtonType::RequestGeoLocation => RequestGeoLocationButton::fromArray($data), + InlineButtonType::Chat => ChatButton::fromArray($data), + default => throw new LogicException("Unknown or unsupported inline button type: " . ($data['type'] ?? 'none')), + }; + } + /** * List of messages. * diff --git a/src/Models/AbstractModel.php b/src/Models/AbstractModel.php index 18a41b1..9098b1a 100644 --- a/src/Models/AbstractModel.php +++ b/src/Models/AbstractModel.php @@ -137,7 +137,7 @@ abstract readonly class AbstractModel return $typeName::from($value); } - if (is_subclass_of($typeName, self::class)) { + if (is_subclass_of($typeName, self::class) && is_array($value)) { return $typeName::fromArray($value); } diff --git a/src/Models/Attachments/AudioAttachment.php b/src/Models/Attachments/AudioAttachment.php new file mode 100644 index 0000000..49c2187 --- /dev/null +++ b/src/Models/Attachments/AudioAttachment.php @@ -0,0 +1,22 @@ + null, 'image_url' => null, ], + ['type' => 'image', 'payload' => ['photo_id' => 1, 'token' => 't', 'url' => 'u']] ], 'markup' => null, ], @@ -548,17 +580,21 @@ final class ModelFactoryTest extends TestCase ]; $message = $this->factory->createMessage($rawData); + $attachments = $message->body->attachments; $this->assertInstanceOf(Message::class, $message); $this->assertInstanceOf(MessageBody::class, $message->body); - $this->assertIsArray($message->body->attachments); - $this->assertCount(2, $message->body->attachments); + $this->assertIsArray($attachments); + $this->assertCount(3, $attachments); - $this->assertInstanceOf(DataAttachment::class, $message->body->attachments[0]); - $this->assertSame('payload_from_reply_button', $message->body->attachments[0]->data); + $this->assertInstanceOf(DataAttachment::class, $attachments[0]); + $this->assertSame('payload_from_reply_button', $attachments[0]->data); $this->assertInstanceOf(ShareAttachment::class, $message->body->attachments[1]); - $this->assertSame('Test Share', $message->body->attachments[1]->title); + $this->assertSame('Test Share', $attachments[1]->title); + + $this->assertInstanceOf(PhotoAttachment::class, $attachments[2]); + $this->assertSame(1, $attachments[2]->payload->photoId); } #[Test] @@ -598,4 +634,158 @@ final class ModelFactoryTest extends TestCase $this->factory->createMarkupElement(['type' => 'brand_new_unsupported_type']); } + + #[Test] + public function createAttachmentCorrectlyHydratesReplyKeyboard(): void + { + $data = [ + 'type' => 'reply_keyboard', + 'buttons' => [ + [['type' => 'message', 'text' => 'Hello']], + [['type' => 'user_contact', 'text' => 'My Contact']] + ] + ]; + + $attachment = $this->factory->createAttachment($data); + + $this->assertInstanceOf(ReplyKeyboardAttachment::class, $attachment); + $this->assertCount(2, $attachment->buttons); + $this->assertInstanceOf(SendMessageButton::class, $attachment->buttons[0][0]); + $this->assertInstanceOf(SendContactButton::class, $attachment->buttons[1][0]); + $this->assertSame('My Contact', $attachment->buttons[1][0]->text); + } + + #[Test] + public function createReplyButtonThrowsExceptionForUnknownType(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Unknown or unsupported reply button type: new_fancy_button'); + + $this->factory->createReplyButton(['type' => 'new_fancy_button']); + } + + /** + * Data provider for successful inline button creation. + * @return array, 1: class-string}> + */ + public static function inlineButtonProvider(): array + { + return [ + 'CallbackButton' => [ + ['type' => 'callback', 'text' => 'Press', 'payload' => 'p'], + CallbackButton::class, + ], + 'LinkButton' => [ + ['type' => 'link', 'text' => 'Visit', 'url' => 'https://a.com'], + LinkButton::class, + ], + 'RequestContactButton' => [ + ['type' => 'request_contact', 'text' => 'Share Contact'], + RequestContactButton::class, + ], + 'RequestGeoLocationButton' => [ + ['type' => 'request_geo_location', 'text' => 'Share Location', 'quick' => false], + RequestGeoLocationButton::class, + ], + 'ChatButton' => [ + ['type' => 'chat', 'text' => 'Join Chat', 'chat_title' => 'My Chat'], + ChatButton::class, + ], + ]; + } + + #[Test] + #[DataProvider('inlineButtonProvider')] + public function createInlineButtonSuccessfully(array $data, string $expectedClass): void + { + $button = $this->factory->createInlineButton($data); + + $this->assertInstanceOf($expectedClass, $button); + $this->assertSame($data['text'], $button->text); + } + + /** + * Data provider for invalid inline button data. + * @return array, 1: string}> + */ + public static function invalidInlineButtonProvider(): array + { + return [ + 'unknown type' => [ + ['type' => 'unknown_button_type', 'text' => 'Unknown'], + 'Unknown or unsupported inline button type: unknown_button_type', + ], + 'missing type' => [ + ['text' => 'No type here'], + 'Unknown or unsupported inline button type: none', + ], + ]; + } + + #[Test] + #[DataProvider('invalidInlineButtonProvider')] + public function createInlineButtonThrowsExceptionForInvalidType(array $invalidData, string $expectedMessage): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage($expectedMessage); + + $this->factory->createInlineButton($invalidData); + } + + /** + * Data provider for testing various attachment types in createAttachment. + * @return array, 1: class-string, 2: callable}> + */ + public static function attachmentTypeProvider(): array + { + return [ + 'Data Attachment' => [ + ['type' => 'data', 'data' => 'test_payload'], + DataAttachment::class, + function (TestCase $test, DataAttachment $attachment) { + $test->assertSame('test_payload', $attachment->data); + } + ], + + 'Location Attachment' => [ + ['type' => 'location', 'latitude' => 55.751244, 'longitude' => 37.618423], + LocationAttachment::class, + function (TestCase $test, LocationAttachment $attachment) { + $test->assertSame(55.751244, $attachment->latitude); + } + ], + + 'Inline Keyboard Attachment' => [ + [ + 'type' => 'inline_keyboard', + 'payload' => [ + 'buttons' => [ + [['type' => 'callback', 'text' => 'Test', 'payload' => 'p']] + ] + ] + ], + InlineKeyboardAttachment::class, + function (TestCase $test, InlineKeyboardAttachment $attachment) { + $test->assertInstanceOf(KeyboardPayload::class, $attachment->payload); + $test->assertIsArray($attachment->payload->buttons); + $test->assertInstanceOf(CallbackButton::class, $attachment->payload->buttons[0][0]); + $test->assertSame('p', $attachment->payload->buttons[0][0]->payload); + } + ], + ]; + } + + #[Test] + #[DataProvider('attachmentTypeProvider')] + public function createAttachmentSuccessfullyCreatesVariousTypes( + array $data, + string $expectedClass, + callable $assertionCallback, + ): void { + $attachment = $this->factory->createAttachment($data); + + $this->assertInstanceOf($expectedClass, $attachment); + + $assertionCallback($this, $attachment); + } } diff --git a/tests/Models/AbstractModelMappingTest.php b/tests/Models/AbstractModelMappingTest.php index dc2ac1c..5c41cfd 100644 --- a/tests/Models/AbstractModelMappingTest.php +++ b/tests/Models/AbstractModelMappingTest.php @@ -7,9 +7,11 @@ namespace BushlanovDev\MaxMessengerBot\Tests\Models; use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf; use BushlanovDev\MaxMessengerBot\Enums\UpdateType; use BushlanovDev\MaxMessengerBot\Models\AbstractModel; +use DateTimeInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use ReflectionClass; use stdClass; #[CoversClass(AbstractModel::class)] @@ -176,6 +178,30 @@ final class AbstractModelMappingTest extends TestCase $this->assertEquals($expectedArray, $resultArray); $this->assertArrayNotHasKey('uninitialized_prop', $resultArray); } + + #[Test] + public function castValueReturnsValueAsIsForUnmanagedScalarAssignedToObjectType(): void + { + $dateString = '2025-08-26'; + + $reflectionClass = new ReflectionClass(ModelForUnmanagedType::class); + $abstractModelReflection = $reflectionClass->getParentClass(); + $castValueMethod = $abstractModelReflection->getMethod('castValue'); + $castValueMethod->setAccessible(true); + + $property = $reflectionClass->getProperty('eventDate'); + + $result = $castValueMethod->invoke(null, $dateString, $property); + + $this->assertSame($dateString, $result); + } +} + +final readonly class ModelForUnmanagedType extends AbstractModel +{ + public function __construct(public DateTimeInterface $eventDate) + { + } } final readonly class DummyModelForUninitializedProperty extends AbstractModel diff --git a/tests/Models/Attachments/AudioAttachmentTest.php b/tests/Models/Attachments/AudioAttachmentTest.php new file mode 100644 index 0000000..b796f70 --- /dev/null +++ b/tests/Models/Attachments/AudioAttachmentTest.php @@ -0,0 +1,26 @@ + 'audio', 'payload' => ['url' => 'u', 'token' => 't'], 'transcription' => 'Hello world.']; + $attachment = AudioAttachment::fromArray($data); + $this->assertInstanceOf(AudioAttachment::class, $attachment); + $this->assertSame('Hello world.', $attachment->transcription); + } +} diff --git a/tests/Models/Attachments/ContactAttachmentTest.php b/tests/Models/Attachments/ContactAttachmentTest.php new file mode 100644 index 0000000..4c0b150 --- /dev/null +++ b/tests/Models/Attachments/ContactAttachmentTest.php @@ -0,0 +1,28 @@ + 'contact', 'payload' => ['vcf_info' => 'vcf', 'max_info' => null]]; + $attachment = ContactAttachment::fromArray($data); + $this->assertInstanceOf(ContactAttachment::class, $attachment); + $this->assertSame('vcf', $attachment->payload->vcfInfo); + } +} diff --git a/tests/Models/Attachments/FileAttachmentTest.php b/tests/Models/Attachments/FileAttachmentTest.php new file mode 100644 index 0000000..b8357e5 --- /dev/null +++ b/tests/Models/Attachments/FileAttachmentTest.php @@ -0,0 +1,32 @@ + 'file', + 'payload' => ['url' => 'u', 'token' => 't'], + 'filename' => 'doc.pdf', + 'size' => 1024, + ]; + $attachment = FileAttachment::fromArray($data); + $this->assertInstanceOf(FileAttachment::class, $attachment); + $this->assertSame('doc.pdf', $attachment->filename); + $this->assertSame(1024, $attachment->size); + } +} diff --git a/tests/Models/Attachments/InlineKeyboardAttachmentTest.php b/tests/Models/Attachments/InlineKeyboardAttachmentTest.php new file mode 100644 index 0000000..ac7b953 --- /dev/null +++ b/tests/Models/Attachments/InlineKeyboardAttachmentTest.php @@ -0,0 +1,32 @@ + 'inline_keyboard', + 'payload' => ['buttons' => [[['type' => 'callback', 'text' => 'Press', 'payload' => 'p']]]], + ]; + $attachment = InlineKeyboardAttachment::fromArray($data); + $this->assertInstanceOf(InlineKeyboardAttachment::class, $attachment); + $this->assertInstanceOf(KeyboardPayload::class, $attachment->payload); + $this->assertCount(1, $attachment->payload->buttons); + } +} diff --git a/tests/Models/Attachments/LocationAttachmentTest.php b/tests/Models/Attachments/LocationAttachmentTest.php new file mode 100644 index 0000000..a381ab5 --- /dev/null +++ b/tests/Models/Attachments/LocationAttachmentTest.php @@ -0,0 +1,23 @@ + 'location', 'latitude' => 55.75, 'longitude' => 37.61]; + $attachment = LocationAttachment::fromArray($data); + $this->assertInstanceOf(LocationAttachment::class, $attachment); + $this->assertSame(55.75, $attachment->latitude); + } +} diff --git a/tests/Models/Attachments/Payloads/ContactAttachmentPayloadTest.php b/tests/Models/Attachments/Payloads/ContactAttachmentPayloadTest.php new file mode 100644 index 0000000..456d2b8 --- /dev/null +++ b/tests/Models/Attachments/Payloads/ContactAttachmentPayloadTest.php @@ -0,0 +1,29 @@ + 101, 'first_name' => 'MaxUser', 'is_bot' => false, 'last_activity_time' => time()] + ); + $payload = new ContactAttachmentPayload('vcf_info_string', $user); + $this->assertSame('vcf_info_string', $payload->vcfInfo); + $this->assertSame($user, $payload->maxInfo); + $this->assertEquals(['vcf_info' => 'vcf_info_string', 'max_info' => $user->toArray()], $payload->toArray()); + } +} diff --git a/tests/Models/Attachments/Payloads/FileAttachmentPayloadTest.php b/tests/Models/Attachments/Payloads/FileAttachmentPayloadTest.php new file mode 100644 index 0000000..8b98366 --- /dev/null +++ b/tests/Models/Attachments/Payloads/FileAttachmentPayloadTest.php @@ -0,0 +1,23 @@ +assertSame('https://example.com/doc.pdf', $payload->url); + $this->assertSame('file_token_456', $payload->token); + $this->assertEquals(['url' => 'https://example.com/doc.pdf', 'token' => 'file_token_456'], $payload->toArray()); + } +} diff --git a/tests/Models/Attachments/Payloads/KeyboardPayloadTest.php b/tests/Models/Attachments/Payloads/KeyboardPayloadTest.php new file mode 100644 index 0000000..53bde72 --- /dev/null +++ b/tests/Models/Attachments/Payloads/KeyboardPayloadTest.php @@ -0,0 +1,65 @@ + 'inline_keyboard', + 'payload' => [ + 'buttons' => [ + [ + ['type' => 'callback', 'text' => 'Press Me', 'payload' => 'payload_123'], + ], + [ + ['type' => 'link', 'text' => 'Docs', 'url' => 'https://dev.max.ru'], + ['type' => 'callback', 'text' => 'Cancel', 'payload' => 'cancel_op'], + ], + ] + ] + ]; + + $factory = new ModelFactory(); + $attachment = $factory->createAttachment($data); + + $this->assertInstanceOf(InlineKeyboardAttachment::class, $attachment); + $this->assertInstanceOf(KeyboardPayload::class, $attachment->payload); + + $buttons = $attachment->payload->buttons; + $this->assertCount(2, $buttons); + + $this->assertCount(1, $buttons[0]); + $this->assertInstanceOf(CallbackButton::class, $buttons[0][0]); + $this->assertSame('payload_123', $buttons[0][0]->payload); + + $this->assertCount(2, $buttons[1]); + $this->assertInstanceOf(LinkButton::class, $buttons[1][0]); + $this->assertSame('https://dev.max.ru', $buttons[1][0]->url); + $this->assertInstanceOf(CallbackButton::class, $buttons[1][1]); + $this->assertSame('cancel_op', $buttons[1][1]->payload); + } +} diff --git a/tests/Models/Attachments/Payloads/MediaAttachmentPayloadTest.php b/tests/Models/Attachments/Payloads/MediaAttachmentPayloadTest.php new file mode 100644 index 0000000..27a4524 --- /dev/null +++ b/tests/Models/Attachments/Payloads/MediaAttachmentPayloadTest.php @@ -0,0 +1,26 @@ +assertSame('https://example.com/video.mp4', $payload->url); + $this->assertSame('video_token_123', $payload->token); + $this->assertEquals( + ['url' => 'https://example.com/video.mp4', 'token' => 'video_token_123'], + $payload->toArray(), + ); + } +} diff --git a/tests/Models/Attachments/Payloads/PhotoAttachmentPayloadTest.php b/tests/Models/Attachments/Payloads/PhotoAttachmentPayloadTest.php index b374086..7e10cde 100644 --- a/tests/Models/Attachments/Payloads/PhotoAttachmentPayloadTest.php +++ b/tests/Models/Attachments/Payloads/PhotoAttachmentPayloadTest.php @@ -4,105 +4,33 @@ declare(strict_types=1); namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads; -use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf; -use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload; -use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken; -use InvalidArgumentException; +use BushlanovDev\MaxMessengerBot\Models\AbstractModel; +use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -#[CoversClass(PhotoAttachmentRequestPayload::class)] -#[UsesClass(PhotoToken::class)] -#[UsesClass(ArrayOf::class)] +#[CoversClass(PhotoAttachmentPayload::class)] +#[UsesClass(AbstractModel::class)] final class PhotoAttachmentPayloadTest extends TestCase { #[Test] - public function canBeCreatedWithUrlOnly(): void + public function canBeCreatedAndSerialized(): void { - $payload = new PhotoAttachmentRequestPayload(url: 'https://example.com/photo.jpg'); - - $this->assertSame('https://example.com/photo.jpg', $payload->url); - $this->assertNull($payload->token); - $this->assertNull($payload->photos); - - $expectedArray = [ - 'url' => 'https://example.com/photo.jpg', - 'token' => null, - 'photos' => null, + $data = [ + 'photo_id' => 987654321, + 'token' => 'some_received_photo_token', + 'url' => 'https://cdn.max.ru/photos/image.jpg' ]; - $this->assertEquals($expectedArray, $payload->toArray()); - } - #[Test] - public function canBeCreatedWithTokenOnly(): void - { - $payload = new PhotoAttachmentRequestPayload(token: 'uploaded_token_abc'); + $payload = PhotoAttachmentPayload::fromArray($data); - $this->assertSame('uploaded_token_abc', $payload->token); - $this->assertNull($payload->url); - $this->assertNull($payload->photos); + $this->assertInstanceOf(PhotoAttachmentPayload::class, $payload); + $this->assertSame(987654321, $payload->photoId); + $this->assertSame('some_received_photo_token', $payload->token); + $this->assertSame('https://cdn.max.ru/photos/image.jpg', $payload->url); - $expectedArray = [ - 'token' => 'uploaded_token_abc', - 'url' => null, - 'photos' => null, - ]; - $this->assertEquals($expectedArray, $payload->toArray()); - } - - #[Test] - public function canBeCreatedWithPhotosOnly(): void - { - $photos = [ - new PhotoToken('token_1'), - new PhotoToken('token_2'), - ]; - $payload = new PhotoAttachmentRequestPayload(photos: $photos); - - $this->assertSame($photos, $payload->photos); - $this->assertNull($payload->url); - $this->assertNull($payload->token); - - $expectedArray = [ - 'photos' => [ - ['token' => 'token_1'], - ['token' => 'token_2'], - ], - 'url' => null, - 'token' => null, - ]; - $this->assertEquals($expectedArray, $payload->toArray()); - } - - /** - * Data provider for invalid constructor arguments. - * - * @return array - */ - public static function invalidPayloadProvider(): array - { - return [ - 'all null (no arguments)' => [null, null, null], - 'url and token provided' => ['https://a.com', 'token123', null], - 'url and photos provided' => ['https://a.com', null, [new PhotoToken('t')]], - 'token and photos provided' => [null, 'token123', [new PhotoToken('t')]], - 'all three arguments provided' => ['https://a.com', 'token123', [new PhotoToken('t')]], - ]; - } - - #[Test] - #[DataProvider('invalidPayloadProvider')] - public function constructorThrowsExceptionForInvalidArguments( - ?string $url, - ?string $token, - ?array $photos - ): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Provide exactly one of "url", "token", or "photos" for PhotoAttachmentRequestPayload.'); - - new PhotoAttachmentRequestPayload($url, $token, $photos); + $this->assertEquals($data, $payload->toArray()); } } diff --git a/tests/Models/Attachments/Payloads/StickerAttachmentPayloadTest.php b/tests/Models/Attachments/Payloads/StickerAttachmentPayloadTest.php new file mode 100644 index 0000000..a931767 --- /dev/null +++ b/tests/Models/Attachments/Payloads/StickerAttachmentPayloadTest.php @@ -0,0 +1,26 @@ +assertSame('https://example.com/sticker.webp', $payload->url); + $this->assertSame('sticker_code_abc', $payload->code); + $this->assertEquals( + ['url' => 'https://example.com/sticker.webp', 'code' => 'sticker_code_abc'], + $payload->toArray(), + ); + } +} diff --git a/tests/Models/Attachments/Payloads/VideoThumbnailTest.php b/tests/Models/Attachments/Payloads/VideoThumbnailTest.php new file mode 100644 index 0000000..f728764 --- /dev/null +++ b/tests/Models/Attachments/Payloads/VideoThumbnailTest.php @@ -0,0 +1,35 @@ + 'https://example.com/video_thumbnail.jpg', + ]; + + $thumbnail = VideoThumbnail::fromArray($data); + + $this->assertInstanceOf(VideoThumbnail::class, $thumbnail); + $this->assertSame('https://example.com/video_thumbnail.jpg', $thumbnail->url); + + $this->assertEquals($data, $thumbnail->toArray()); + + $directInstance = new VideoThumbnail('https://example.com/video_thumbnail.jpg'); + $this->assertSame('https://example.com/video_thumbnail.jpg', $directInstance->url); + } +} diff --git a/tests/Models/Attachments/PhotoAttachmentTest.php b/tests/Models/Attachments/PhotoAttachmentTest.php new file mode 100644 index 0000000..9ea5662 --- /dev/null +++ b/tests/Models/Attachments/PhotoAttachmentTest.php @@ -0,0 +1,27 @@ + 'image', 'payload' => ['photo_id' => 1, 'token' => 't', 'url' => 'u']]; + $attachment = PhotoAttachment::fromArray($data); + $this->assertInstanceOf(PhotoAttachment::class, $attachment); + $this->assertInstanceOf(PhotoAttachmentPayload::class, $attachment->payload); + $this->assertSame(1, $attachment->payload->photoId); + } +} diff --git a/tests/Models/Attachments/ReplyKeyboardAttachmentTest.php b/tests/Models/Attachments/ReplyKeyboardAttachmentTest.php new file mode 100644 index 0000000..e894068 --- /dev/null +++ b/tests/Models/Attachments/ReplyKeyboardAttachmentTest.php @@ -0,0 +1,43 @@ + 'reply_keyboard', + 'buttons' => [ + [['type' => 'message', 'text' => 'Hello']], + [['type' => 'user_contact', 'text' => 'My Contact']] + ] + ]; + + $factory = new ModelFactory(); + $attachment = $factory->createAttachment($data); + + $this->assertInstanceOf(ReplyKeyboardAttachment::class, $attachment); + $this->assertCount(2, $attachment->buttons); + $this->assertInstanceOf(SendContactButton::class, $attachment->buttons[1][0]); + $this->assertSame('My Contact', $attachment->buttons[1][0]->text); + } +} diff --git a/tests/Models/Attachments/StickerAttachmentTest.php b/tests/Models/Attachments/StickerAttachmentTest.php new file mode 100644 index 0000000..52489c3 --- /dev/null +++ b/tests/Models/Attachments/StickerAttachmentTest.php @@ -0,0 +1,27 @@ + 'sticker', 'payload' => ['url' => 'u', 'code' => 'c'], 'width' => 128, 'height' => 128]; + $attachment = StickerAttachment::fromArray($data); + $this->assertInstanceOf(StickerAttachment::class, $attachment); + $this->assertSame(128, $attachment->width); + $this->assertSame('c', $attachment->payload->code); + } +} diff --git a/tests/Models/Attachments/VideoAttachmentTest.php b/tests/Models/Attachments/VideoAttachmentTest.php new file mode 100644 index 0000000..e58b54d --- /dev/null +++ b/tests/Models/Attachments/VideoAttachmentTest.php @@ -0,0 +1,37 @@ + 'video', + 'payload' => ['url' => 'u', 'token' => 't'], + 'thumbnail' => ['url' => 'thumb_url'], + 'width' => 1920, + 'height' => 1080, + 'duration' => 120, + ]; + $attachment = VideoAttachment::fromArray($data); + $this->assertInstanceOf(VideoAttachment::class, $attachment); + $this->assertInstanceOf(MediaAttachmentPayload::class, $attachment->payload); + $this->assertInstanceOf(VideoThumbnail::class, $attachment->thumbnail); + $this->assertSame(120, $attachment->duration); + } +} diff --git a/tests/Models/MessageStatTest.php b/tests/Models/MessageStatTest.php new file mode 100644 index 0000000..f9ac4cb --- /dev/null +++ b/tests/Models/MessageStatTest.php @@ -0,0 +1,25 @@ + 1234]; + $stat = MessageStat::fromArray($data); + + $this->assertInstanceOf(MessageStat::class, $stat); + $this->assertSame(1234, $stat->views); + $this->assertEquals($data, $stat->toArray()); + } +} diff --git a/tests/Models/MessageTest.php b/tests/Models/MessageTest.php index ae9949d..2c60154 100644 --- a/tests/Models/MessageTest.php +++ b/tests/Models/MessageTest.php @@ -7,6 +7,7 @@ namespace BushlanovDev\MaxMessengerBot\Tests\Models; use BushlanovDev\MaxMessengerBot\Models\LinkedMessage; use BushlanovDev\MaxMessengerBot\Models\Message; use BushlanovDev\MaxMessengerBot\Models\MessageBody; +use BushlanovDev\MaxMessengerBot\Models\MessageStat; use BushlanovDev\MaxMessengerBot\Models\Recipient; use BushlanovDev\MaxMessengerBot\Models\User; use PHPUnit\Framework\Attributes\CoversClass; @@ -19,6 +20,7 @@ use PHPUnit\Framework\TestCase; #[UsesClass(Recipient::class)] #[UsesClass(User::class)] #[UsesClass(LinkedMessage::class)] +#[UsesClass(MessageStat::class)] final class MessageTest extends TestCase { #[Test] @@ -59,6 +61,9 @@ final class MessageTest extends TestCase 'sender' => null, 'chat_id' => null, ], + 'stat' => [ + 'views' => 500, + ], ]; $message = Message::fromArray($data); @@ -69,6 +74,8 @@ final class MessageTest extends TestCase $this->assertInstanceOf(Recipient::class, $message->recipient); $this->assertInstanceOf(User::class, $message->sender); $this->assertSame($data['url'], $message->url); + $this->assertInstanceOf(MessageStat::class, $message->stat); + $this->assertSame(500, $message->stat->views); $array = $message->toArray(); diff --git a/tests/WebhookHandlerTest.php b/tests/WebhookHandlerTest.php index 05a523d..cc601ee 100644 --- a/tests/WebhookHandlerTest.php +++ b/tests/WebhookHandlerTest.php @@ -84,6 +84,7 @@ final class WebhookHandlerTest extends TestCase null, null, null, + null, ); return new MessageCreatedUpdate(