mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-17 20:29:57 +00:00
Added AudioAttachmentRequest & ContactAttachmentRequest & FileAttachmentRequest & LocationAttachmentRequest & ShareAttachmentRequest & StickerAttachmentRequest & VideoAttachmentRequest
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
[](LICENSE)
|
||||
|
||||
> [!CAUTION]
|
||||
> На мой взгляд `Max Messenger` является ни чем иным как малварью, созданной для слежки за гражданами РФ.
|
||||
> Настоятельно не рекомендую использовать на реальных устройствах, с настоящим номером телефона, и для личной переписки.
|
||||
> На мой взгляд `Max Messenger` является ни чем иным как малварью, созданной для слежки за гражданами РФ. Настоятельно
|
||||
> не рекомендую использовать его на реальных устройствах, с настоящим номером телефона, и для личной переписки.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Библиотека в стадии активной разработки.
|
||||
@@ -25,46 +25,46 @@
|
||||
|
||||
#### Bots
|
||||
|
||||
- `[x] GET /me` (`getBotInfo`) — *Получение информации о боте.*
|
||||
- `[ ] PATCH /me` (`editBotInfo`) — *Редактирование информации о боте.*
|
||||
- [x] `GET /me` (`getBotInfo`) — *Получение информации о боте.*
|
||||
- [ ] `PATCH /me` (`editBotInfo`) — *Редактирование информации о боте.*
|
||||
|
||||
#### Chats
|
||||
|
||||
- `[ ] GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
|
||||
- `[ ] GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
|
||||
- `[x] GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
|
||||
- `[ ] PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
|
||||
- `[ ] DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
|
||||
- `[ ] POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
|
||||
- `[ ] GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
|
||||
- `[ ] PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
|
||||
- `[ ] DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
|
||||
- `[ ] GET /chats/{chatId}/members/me` (`getMembership`) — *Получение информации о членстве бота в чате.*
|
||||
- `[ ] DELETE /chats/{chatId}/members/me` (`leaveChat`) — *Выход бота из чата.*
|
||||
- `[ ] GET /chats/{chatId}/members/admins` (`getAdmins`) — *Получение администраторов чата.*
|
||||
- `[ ] POST /chats/{chatId}/members/admins` (`postAdmins`) — *Назначение администраторов чата.*
|
||||
- `[ ] DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) — *Снятие прав администратора.*
|
||||
- `[ ] GET /chats/{chatId}/members` (`getMembers`) — *Получение участников чата.*
|
||||
- `[ ] POST /chats/{chatId}/members` (`addMembers`) — *Добавление участников в чат.*
|
||||
- `[ ] DELETE /chats/{chatId}/members` (`removeMember`) — *Удаление участника из чата.*
|
||||
- [ ] `GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
|
||||
- [ ] `GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
|
||||
- [x] `GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
|
||||
- [ ] `PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
|
||||
- [ ] `DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
|
||||
- [ ] `POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
|
||||
- [ ] `GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
|
||||
- [ ] `PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
|
||||
- [ ] `DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
|
||||
- [ ] `GET /chats/{chatId}/members/me` (`getMembership`) — *Получение информации о членстве бота в чате.*
|
||||
- [ ] `DELETE /chats/{chatId}/members/me` (`leaveChat`) — *Выход бота из чата.*
|
||||
- [ ] `GET /chats/{chatId}/members/admins` (`getAdmins`) — *Получение администраторов чата.*
|
||||
- [ ] `POST /chats/{chatId}/members/admins` (`postAdmins`) — *Назначение администраторов чата.*
|
||||
- [ ] `DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) — *Снятие прав администратора.*
|
||||
- [ ] `GET /chats/{chatId}/members` (`getMembers`) — *Получение участников чата.*
|
||||
- [ ] `POST /chats/{chatId}/members` (`addMembers`) — *Добавление участников в чат.*
|
||||
- [ ] `DELETE /chats/{chatId}/members` (`removeMember`) — *Удаление участника из чата.*
|
||||
|
||||
#### Subscriptions
|
||||
|
||||
- `[x] GET /subscriptions` (`getSubscriptions`) — *Получение списка Webhook-подписок.*
|
||||
- `[x] POST /subscriptions` (`subscribe`) — *Создание Webhook-подписки.*
|
||||
- `[x] DELETE /subscriptions` (`unsubscribe`) — *Удаление Webhook-подписки.*
|
||||
- `[x] GET /updates` (`getUpdates`) — *Получение обновлений через Long-Polling.*
|
||||
- [x] `GET /subscriptions` (`getSubscriptions`) — *Получение списка Webhook-подписок.*
|
||||
- [x] `POST /subscriptions` (`subscribe`) — *Создание Webhook-подписки.*
|
||||
- [x] `DELETE /subscriptions` (`unsubscribe`) — *Удаление Webhook-подписки.*
|
||||
- [x] `GET /updates` (`getUpdates`) — *Получение обновлений через Long-Polling.*
|
||||
|
||||
#### Upload
|
||||
|
||||
- `[x] POST /uploads` (`getUploadUrl`) — *Получение URL для загрузки файла.*
|
||||
- [x] `POST /uploads` (`getUploadUrl`) — *Получение URL для загрузки файла.*
|
||||
|
||||
#### Messages
|
||||
|
||||
- `[ ] GET /messages` (`getMessages`) — *Получение списка сообщений из чата.*
|
||||
- `[x] POST /messages` (`sendMessage`) — *Отправка сообщения.*
|
||||
- `[ ] PUT /messages` (`editMessage`) — *Редактирование сообщения.*
|
||||
- `[ ] DELETE /messages` (`deleteMessage`) — *Удаление сообщения.*
|
||||
- `[ ] GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
|
||||
- `[ ] GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
|
||||
- `[ ] POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*
|
||||
- [ ] `GET /messages` (`getMessages`) — *Получение списка сообщений из чата.*
|
||||
- [x] `POST /messages` (`sendMessage`) — *Отправка сообщения.*
|
||||
- [ ] `PUT /messages` (`editMessage`) — *Редактирование сообщения.*
|
||||
- [ ] `DELETE /messages` (`deleteMessage`) — *Удаление сообщения.*
|
||||
- [ ] `GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
|
||||
- [ ] `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
|
||||
- [ ] `POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*
|
||||
|
||||
+3
-3
@@ -9,13 +9,13 @@
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<path fill="#555" d="M0 0h63v20H0z"/>
|
||||
<path fill="#97CA00" d="M63 0h36v20H63z"/>
|
||||
<path fill="#4c1" d="M63 0h36v20H63z"/>
|
||||
<path fill="url(#b)" d="M0 0h99v20H0z"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||
<text x="31.5" y="14">coverage</text>
|
||||
<text x="80" y="15" fill="#010101" fill-opacity=".3">94%</text>
|
||||
<text x="80" y="14">94%</text>
|
||||
<text x="80" y="15" fill="#010101" fill-opacity=".3">100%</text>
|
||||
<text x="80" y="14">100%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 902 B |
@@ -24,6 +24,8 @@
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.77",
|
||||
"jaschilz/php-coverage-badger": "^2.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"php-mock/php-mock-phpunit": "^2.13",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"roave/security-advisories": "dev-latest"
|
||||
|
||||
+7
-3
@@ -13,7 +13,10 @@ use BushlanovDev\MaxMessengerBot\Exceptions\SecurityException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AudioAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\FileAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
@@ -71,12 +74,10 @@ 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();
|
||||
@@ -429,7 +430,7 @@ class Api
|
||||
throw new InvalidArgumentException("File not found or not readable: $filePath");
|
||||
}
|
||||
|
||||
$fileHandle = fopen($filePath, 'r');
|
||||
$fileHandle = @fopen($filePath, 'r');
|
||||
if ($fileHandle === false) {
|
||||
throw new RuntimeException("Could not open file for reading: $filePath");
|
||||
}
|
||||
@@ -450,6 +451,9 @@ class Api
|
||||
|
||||
return match ($type) {
|
||||
UploadType::Image => PhotoAttachmentRequest::fromToken($uploadResult['token']),
|
||||
UploadType::Video => new VideoAttachmentRequest($uploadResult['token']),
|
||||
UploadType::Audio => new AudioAttachmentRequest($uploadResult['token']),
|
||||
UploadType::File => new FileAttachmentRequest($uploadResult['token']), // @phpstan-ignore-line
|
||||
default => throw new LogicException(
|
||||
"Attachment creation for type '$type->value' is not yet implemented."
|
||||
),
|
||||
|
||||
@@ -11,7 +11,7 @@ enum AttachmentType: string
|
||||
case Audio = 'audio';
|
||||
case File = 'file';
|
||||
case Sticker = 'sticker';
|
||||
case Contract = 'contract';
|
||||
case Contact = 'contact';
|
||||
case InlineKeyboard = 'inline_keyboard';
|
||||
case Location = 'location';
|
||||
case Share = 'share';
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
abstract readonly class AbstractAttachmentPayload extends AbstractModel
|
||||
abstract readonly class AbstractAttachmentRequestPayload extends AbstractModel
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for a contact attachment request.
|
||||
*/
|
||||
final readonly class ContactAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string|null $name Contact name.
|
||||
* @param int|null $contactId Contact identifier if it is a registered Max user.
|
||||
* @param string|null $vcfInfo Full information about the contact in VCF format.
|
||||
* @param string|null $vcfPhone Contact phone in VCF format.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $name = null,
|
||||
public ?int $contactId = null,
|
||||
public ?string $vcfInfo = null,
|
||||
public ?string $vcfPhone = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\AbstractButton;
|
||||
|
||||
final readonly class InlineKeyboardPayload extends AbstractAttachmentPayload
|
||||
final readonly class InlineKeyboardAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param AbstractButton[][] $buttons
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for a location attachment request.
|
||||
*/
|
||||
final readonly class LocationAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param float $latitude Latitude as a floating-point number.
|
||||
* @param float $longitude Longitude as a floating-point number.
|
||||
*/
|
||||
public function __construct(
|
||||
public float $latitude,
|
||||
public float $longitude,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -10,7 +10,7 @@ use InvalidArgumentException;
|
||||
/**
|
||||
* Request to attach image. All fields are mutually exclusive.
|
||||
*/
|
||||
final readonly class PhotoAttachmentPayload extends AbstractAttachmentPayload
|
||||
final readonly class PhotoAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string|null $url Any external image URL you want to attach.
|
||||
@@ -25,7 +25,7 @@ final readonly class PhotoAttachmentPayload extends AbstractAttachmentPayload
|
||||
) {
|
||||
if (count(array_filter([$this->url, $this->token, $this->photos])) !== 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Provide exactly one of "url", "token", or "photos" for PhotoAttachmentPayload.'
|
||||
'Provide exactly one of "url", "token", or "photos" for PhotoAttachmentRequestPayload.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Payload for a share (URL preview) attachment request.
|
||||
*/
|
||||
final readonly class ShareAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string|null $url URL to be attached to the message for media preview.
|
||||
* @param string|null $token Token of a previously generated share attachment.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $url = null,
|
||||
public ?string $token = null,
|
||||
) {
|
||||
if (count(array_filter([$this->url, $this->token])) !== 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Provide exactly one of "url" or "token" for ShareAttachmentRequestPayload.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for a sticker attachment request.
|
||||
*/
|
||||
final readonly class StickerAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string $code The unique code of the sticker to be sent.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $code,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for attachments that are uploaded to the server first (video, audio, file).
|
||||
*/
|
||||
final readonly class UploadedInfoAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful file upload.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\AbstractAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\AbstractAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Message attachments.
|
||||
@@ -15,7 +15,7 @@ abstract readonly class AbstractAttachmentRequest extends AbstractModel
|
||||
{
|
||||
public function __construct(
|
||||
public AttachmentType $type,
|
||||
public AbstractAttachmentPayload $payload,
|
||||
public AbstractAttachmentRequestPayload $payload,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach an audio file to a message.
|
||||
*/
|
||||
final readonly class AudioAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful audio file upload.
|
||||
*/
|
||||
public function __construct(string $token)
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::Audio,
|
||||
new UploadedInfoAttachmentRequestPayload($token),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a contact card to a message.
|
||||
*/
|
||||
final readonly class ContactAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param string|null $name Contact name.
|
||||
* @param int|null $contactId Contact identifier if it is a registered Max user.
|
||||
* @param string|null $vcfInfo Full information about the contact in VCF format.
|
||||
* @param string|null $vcfPhone Contact phone in VCF format.
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?int $contactId = null,
|
||||
?string $vcfInfo = null,
|
||||
?string $vcfPhone = null
|
||||
) {
|
||||
parent::__construct(
|
||||
AttachmentType::Contact,
|
||||
new ContactAttachmentRequestPayload($name, $contactId, $vcfInfo, $vcfPhone),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a generic file to a message.
|
||||
*/
|
||||
final readonly class FileAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful file upload.
|
||||
*/
|
||||
public function __construct(string $token)
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::File,
|
||||
new UploadedInfoAttachmentRequestPayload($token),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\AbstractButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
|
||||
final readonly class InlineKeyboardAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
@@ -17,7 +17,7 @@ final readonly class InlineKeyboardAttachmentRequest extends AbstractAttachmentR
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::InlineKeyboard,
|
||||
new InlineKeyboardPayload($buttons)
|
||||
new InlineKeyboardAttachmentRequestPayload($buttons),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\LocationAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a geographical location to a message.
|
||||
*/
|
||||
final readonly class LocationAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param float $latitude Latitude as a floating-point number.
|
||||
* @param float $longitude Longitude as a floating-point number.
|
||||
*/
|
||||
public function __construct(float $latitude, float $longitude)
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::Location,
|
||||
new LocationAttachmentRequestPayload($latitude, $longitude),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ final readonly class PhotoAttachmentRequest extends AbstractAttachmentRequest
|
||||
*/
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(url: $url));
|
||||
return new self(new PhotoAttachmentRequestPayload(url: $url));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ final readonly class PhotoAttachmentRequest extends AbstractAttachmentRequest
|
||||
*/
|
||||
public static function fromToken(string $token): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(token: $token));
|
||||
return new self(new PhotoAttachmentRequestPayload(token: $token));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,13 +46,13 @@ final readonly class PhotoAttachmentRequest extends AbstractAttachmentRequest
|
||||
*/
|
||||
public static function fromPhotos(array $photos): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(photos: $photos));
|
||||
return new self(new PhotoAttachmentRequestPayload(photos: $photos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhotoAttachmentPayload $payload Request to attach image.
|
||||
* @param PhotoAttachmentRequestPayload $payload Request to attach image.
|
||||
*/
|
||||
private function __construct(PhotoAttachmentPayload $payload)
|
||||
private function __construct(PhotoAttachmentRequestPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Image, $payload);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a media preview of an external URL.
|
||||
*/
|
||||
final readonly class ShareAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* Creates a request to attach a URL preview.
|
||||
*
|
||||
* @param string $url The URL to generate a preview for.
|
||||
*
|
||||
* @return ShareAttachmentRequest
|
||||
*/
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
return new self(new ShareAttachmentRequestPayload(url: $url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request to re-send a URL preview using its token.
|
||||
*
|
||||
* @param string $token The token of a previously generated preview.
|
||||
*
|
||||
* @return ShareAttachmentRequest
|
||||
*/
|
||||
public static function fromToken(string $token): self
|
||||
{
|
||||
return new self(new ShareAttachmentRequestPayload(token: $token));
|
||||
}
|
||||
|
||||
private function __construct(ShareAttachmentRequestPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Share, $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\StickerAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a sticker to a message.
|
||||
*/
|
||||
final readonly class StickerAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param string $code The unique code of the sticker.
|
||||
*/
|
||||
public function __construct(string $code)
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::Sticker,
|
||||
new StickerAttachmentRequestPayload($code),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Request to attach a video to a message.
|
||||
*/
|
||||
final readonly class VideoAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful video upload.
|
||||
*/
|
||||
public function __construct(string $token)
|
||||
{
|
||||
parent::__construct(
|
||||
AttachmentType::Video,
|
||||
new UploadedInfoAttachmentRequestPayload($token),
|
||||
);
|
||||
}
|
||||
}
|
||||
+12
-14
@@ -41,7 +41,7 @@ final class WebhookHandler
|
||||
* @param callable $handler The function to execute when the update is received.
|
||||
* The handler will receive the specific Update object (e.g., MessageCreatedUpdate) and the Api instance.
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
*/
|
||||
public function addHandler(UpdateType $type, callable $handler): self
|
||||
{
|
||||
@@ -55,7 +55,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\MessageCreatedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
*/
|
||||
public function onMessageCreated(callable $handler): self
|
||||
{
|
||||
@@ -67,7 +67,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\MessageCallbackUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onMessageCallback(callable $handler): self
|
||||
@@ -80,7 +80,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\MessageEditedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onMessageEdited(callable $handler): self
|
||||
@@ -93,7 +93,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\MessageRemovedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onMessageRemoved(callable $handler): self
|
||||
@@ -106,7 +106,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\BotAddedToChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onBotAdded(callable $handler): self
|
||||
@@ -119,7 +119,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\BotRemovedFromChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onBotRemoved(callable $handler): self
|
||||
@@ -132,7 +132,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\UserAddedToChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onUserAdded(callable $handler): self
|
||||
@@ -145,7 +145,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\UserRemovedFromChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onUserRemoved(callable $handler): self
|
||||
@@ -158,7 +158,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\BotStartedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onBotStarted(callable $handler): self
|
||||
@@ -171,7 +171,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\ChatTitleChangedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onChatTitleChanged(callable $handler): self
|
||||
@@ -184,7 +184,7 @@ final class WebhookHandler
|
||||
*
|
||||
* @param callable(Models\Updates\MessageChatCreatedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @return WebhookHandler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onMessageChatCreated(callable $handler): self
|
||||
@@ -227,12 +227,10 @@ 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();
|
||||
}
|
||||
|
||||
+449
-4
@@ -12,13 +12,26 @@ use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\LocationAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\StickerAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AudioAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\ContactAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\FileAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\InlineKeyboardAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\LocationAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\ShareAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\StickerAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
@@ -36,13 +49,19 @@ use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use phpmock\phpunit\PHPMock;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
|
||||
#[CoversClass(Api::class)]
|
||||
#[UsesClass(Result::class)]
|
||||
@@ -54,11 +73,11 @@ use ReflectionClass;
|
||||
#[UsesClass(Recipient::class)]
|
||||
#[UsesClass(Sender::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(InlineKeyboardPayload::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequestPayload::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequest::class)]
|
||||
#[UsesClass(PhotoToken::class)]
|
||||
#[UsesClass(PhotoAttachmentRequest::class)]
|
||||
#[UsesClass(PhotoAttachmentPayload::class)]
|
||||
#[UsesClass(PhotoAttachmentRequestPayload::class)]
|
||||
#[UsesClass(UploadEndpoint::class)]
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(UpdateList::class)]
|
||||
@@ -67,8 +86,22 @@ use ReflectionClass;
|
||||
#[UsesClass(AbstractUpdate::class)]
|
||||
#[UsesClass(BotStartedUpdate::class)]
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
#[UsesClass(UploadedInfoAttachmentRequestPayload::class)]
|
||||
#[UsesClass(VideoAttachmentRequest::class)]
|
||||
#[UsesClass(AudioAttachmentRequest::class)]
|
||||
#[UsesClass(FileAttachmentRequest::class)]
|
||||
#[UsesClass(StickerAttachmentRequest::class)]
|
||||
#[UsesClass(StickerAttachmentRequestPayload::class)]
|
||||
#[UsesClass(ContactAttachmentRequest::class)]
|
||||
#[UsesClass(ContactAttachmentRequestPayload::class)]
|
||||
#[UsesClass(LocationAttachmentRequest::class)]
|
||||
#[UsesClass(LocationAttachmentRequestPayload::class)]
|
||||
#[UsesClass(ShareAttachmentRequest::class)]
|
||||
#[UsesClass(ShareAttachmentRequestPayload::class)]
|
||||
final class ApiTest extends TestCase
|
||||
{
|
||||
use PHPMock;
|
||||
|
||||
private MockObject&ClientApiInterface $clientMock;
|
||||
private MockObject&ModelFactory $modelFactoryMock;
|
||||
private Api $api;
|
||||
@@ -752,4 +785,416 @@ final class ApiTest extends TestCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[RunInSeparateProcess]
|
||||
#[PreserveGlobalState(false)]
|
||||
public function constructorThrowsExceptionWhenGuzzleIsMissing(): void
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessageMatches('/"guzzlehttp\/guzzle" is not found/');
|
||||
|
||||
$classExistsMock = $this->getFunctionMock('BushlanovDev\MaxMessengerBot', 'class_exists');
|
||||
|
||||
$classExistsMock->expects($this->once())
|
||||
->with(\GuzzleHttp\Client::class)
|
||||
->willReturn(false);
|
||||
|
||||
new Api('some-token');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleUpdatesLoopCatchesGenericExceptionAndContinues(): void
|
||||
{
|
||||
$this->processUpdatesBatchCallCount = 0;
|
||||
$handlers = [UpdateType::MessageCreated->value => fn() => null];
|
||||
|
||||
$apiMock = $this->getMockBuilder(Api::class)
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock])
|
||||
->onlyMethods(['processUpdatesBatch'])
|
||||
->getMock();
|
||||
|
||||
$apiMock->expects($this->any())
|
||||
->method('processUpdatesBatch')
|
||||
->willReturnCallback(function () {
|
||||
switch ($this->processUpdatesBatchCallCount++) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
throw new \BushlanovDev\MaxMessengerBot\Exceptions\SerializationException(
|
||||
"Simulated JSON error"
|
||||
);
|
||||
default:
|
||||
throw new \Error("LoopBreak");
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectOutputRegex('/An error occurred: Simulated JSON error/');
|
||||
|
||||
try {
|
||||
$apiMock->handleUpdates($handlers);
|
||||
} catch (\Error $e) {
|
||||
$this->assertSame('LoopBreak', $e->getMessage());
|
||||
$this->assertSame(
|
||||
3,
|
||||
$this->processUpdatesBatchCallCount,
|
||||
'processUpdatesBatch should have been called 3 times, indicating the loop continued after the exception.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentThrowsRuntimeExceptionWhenPathIsADirectory(): void
|
||||
{
|
||||
$root = vfsStream::setup('root');
|
||||
$directory = vfsStream::newDirectory('my_dir')->at($root);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessageMatches('/Could not open file for reading/');
|
||||
|
||||
$this->api->uploadAttachment(UploadType::File, $directory->url());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentThrowsSerializationExceptionForInvalidUploadResponse(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_upload_');
|
||||
file_put_contents($filePath, 'content');
|
||||
|
||||
$uploadType = UploadType::Image;
|
||||
$uploadUrl = 'https://upload.server/path';
|
||||
|
||||
$getUploadUrlResponse = ['url' => $uploadUrl];
|
||||
$expectedEndpoint = new UploadEndpoint($uploadUrl);
|
||||
|
||||
$invalidUploadResponse = ['status' => 'error', 'message' => 'Upload failed'];
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/uploads', ['type' => $uploadType->value])
|
||||
->willReturn($getUploadUrlResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUploadEndpoint')
|
||||
->with($getUploadUrlResponse)
|
||||
->willReturn($expectedEndpoint);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('upload')
|
||||
->with($uploadUrl, $this->isResource(), basename($filePath))
|
||||
->willReturn($invalidUploadResponse);
|
||||
|
||||
$this->expectException(SerializationException::class);
|
||||
$this->expectExceptionMessage('Could not find "token" in upload server response.');
|
||||
|
||||
try {
|
||||
$this->api->uploadAttachment($uploadType, $filePath);
|
||||
} finally {
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentSuccessfullyUploadsVideoAndReturnsAttachment(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_video_');
|
||||
file_put_contents($filePath, 'fake-video-content');
|
||||
|
||||
$uploadType = UploadType::Video;
|
||||
$uploadUrl = 'https://upload.server/video_path';
|
||||
$uploadToken = 'VIDEO_TOKEN_XYZ';
|
||||
|
||||
$getUploadUrlResponse = ['url' => $uploadUrl];
|
||||
$uploadResponse = ['token' => $uploadToken];
|
||||
$expectedEndpoint = new UploadEndpoint($uploadUrl);
|
||||
$expectedAttachment = new VideoAttachmentRequest($uploadToken);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/uploads', ['type' => $uploadType->value])
|
||||
->willReturn($getUploadUrlResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUploadEndpoint')
|
||||
->with($getUploadUrlResponse)
|
||||
->willReturn($expectedEndpoint);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('upload')
|
||||
->with($uploadUrl, $this->isResource(), basename($filePath))
|
||||
->willReturn($uploadResponse);
|
||||
|
||||
$result = $this->api->uploadAttachment($uploadType, $filePath);
|
||||
|
||||
$this->assertEquals($expectedAttachment, $result);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentSuccessfullyUploadsAudioAndReturnsAttachment(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_audio_');
|
||||
file_put_contents($filePath, 'fake-audio-content');
|
||||
|
||||
$uploadType = UploadType::Audio;
|
||||
$uploadUrl = 'https://upload.server/audio_path';
|
||||
$uploadToken = 'AUDIO_TOKEN_ABC';
|
||||
|
||||
$getUploadUrlResponse = ['url' => $uploadUrl];
|
||||
$uploadResponse = ['token' => $uploadToken];
|
||||
$expectedEndpoint = new UploadEndpoint($uploadUrl);
|
||||
$expectedAttachment = new AudioAttachmentRequest($uploadToken);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/uploads', ['type' => $uploadType->value])
|
||||
->willReturn($getUploadUrlResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUploadEndpoint')
|
||||
->with($getUploadUrlResponse)
|
||||
->willReturn($expectedEndpoint);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('upload')
|
||||
->with($uploadUrl, $this->isResource(), basename($filePath))
|
||||
->willReturn($uploadResponse);
|
||||
|
||||
$result = $this->api->uploadAttachment($uploadType, $filePath);
|
||||
|
||||
$this->assertEquals($expectedAttachment, $result);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentSuccessfullyUploadsFileAndReturnsAttachment(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_file_');
|
||||
file_put_contents($filePath, 'fake-file-content');
|
||||
|
||||
$uploadType = UploadType::File;
|
||||
$uploadUrl = 'https://upload.server/file_path';
|
||||
$uploadToken = 'FILE_TOKEN_QWERTY';
|
||||
|
||||
$getUploadUrlResponse = ['url' => $uploadUrl];
|
||||
$uploadResponse = ['token' => $uploadToken];
|
||||
$expectedEndpoint = new UploadEndpoint($uploadUrl);
|
||||
$expectedAttachment = new FileAttachmentRequest($uploadToken);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/uploads', ['type' => $uploadType->value])
|
||||
->willReturn($getUploadUrlResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUploadEndpoint')
|
||||
->with($getUploadUrlResponse)
|
||||
->willReturn($expectedEndpoint);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('upload')
|
||||
->with($uploadUrl, $this->isResource(), basename($filePath))
|
||||
->willReturn($uploadResponse);
|
||||
|
||||
$result = $this->api->uploadAttachment($uploadType, $filePath);
|
||||
|
||||
$this->assertEquals($expectedAttachment, $result);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function sendMessageWithStickerAttachmentBuildsCorrectRequest(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$stickerCode = 'sticker_id_ok';
|
||||
$stickerRequest = new StickerAttachmentRequest($stickerCode);
|
||||
|
||||
$expectedBody = [
|
||||
'attachments' => [
|
||||
[
|
||||
'type' => 'sticker',
|
||||
'payload' => [
|
||||
'code' => $stickerCode,
|
||||
],
|
||||
],
|
||||
],
|
||||
'notify' => true,
|
||||
];
|
||||
$expectedQuery = ['chat_id' => $chatId, 'disable_link_preview' => false];
|
||||
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.sticker.1', 'seq' => 10],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$stickerRequest]);
|
||||
|
||||
$this->assertSame($expectedMessageObject, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function sendMessageWithContactAttachmentBuildsCorrectRequest(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$contactRequest = new ContactAttachmentRequest(name: 'Service Desk', vcfPhone: '555-1234');
|
||||
|
||||
$expectedBody = [
|
||||
'attachments' => [
|
||||
[
|
||||
'type' => 'contact',
|
||||
'payload' => [
|
||||
'name' => 'Service Desk',
|
||||
'contact_id' => null,
|
||||
'vcf_info' => null,
|
||||
'vcf_phone' => '555-1234',
|
||||
],
|
||||
],
|
||||
],
|
||||
'notify' => true,
|
||||
];
|
||||
$expectedQuery = ['chat_id' => $chatId, 'disable_link_preview' => false];
|
||||
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.contact.1', 'seq' => 11],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$contactRequest]);
|
||||
|
||||
$this->assertSame($expectedMessageObject, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function sendMessageWithLocationAttachmentBuildsCorrectRequest(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$latitude = 59.9343;
|
||||
$longitude = 30.3351;
|
||||
$locationRequest = new LocationAttachmentRequest($latitude, $longitude);
|
||||
|
||||
$expectedBody = [
|
||||
'attachments' => [
|
||||
[
|
||||
'type' => 'location',
|
||||
'payload' => [
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
],
|
||||
],
|
||||
],
|
||||
'notify' => true,
|
||||
];
|
||||
$expectedQuery = ['chat_id' => $chatId, 'disable_link_preview' => false];
|
||||
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.location.1', 'seq' => 12],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$locationRequest]);
|
||||
|
||||
$this->assertSame($expectedMessageObject, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function sendMessageWithShareAttachmentBuildsCorrectRequest(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$url = 'https://dev.max.ru';
|
||||
$shareRequest = ShareAttachmentRequest::fromUrl($url);
|
||||
|
||||
$expectedBody = [
|
||||
'attachments' => [
|
||||
[
|
||||
'type' => 'share',
|
||||
'payload' => [
|
||||
'url' => $url,
|
||||
'token' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
'notify' => true,
|
||||
];
|
||||
$expectedQuery = ['chat_id' => $chatId, 'disable_link_preview' => false];
|
||||
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.share.1', 'seq' => 13],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$shareRequest]);
|
||||
|
||||
$this->assertSame($expectedMessageObject, $result);
|
||||
}
|
||||
}
|
||||
|
||||
+76
-7
@@ -280,13 +280,18 @@ final class ClientTest extends TestCase
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->method('withBody')
|
||||
->with($this->callback(function (StreamInterface $stream) use ($fileContents, $fileName) {
|
||||
$stream->rewind();
|
||||
$body = $stream->getContents();
|
||||
$this->assertStringContainsString('Content-Disposition: form-data; name="data"; filename="' . $fileName . '"', $body);
|
||||
$this->assertStringContainsString($fileContents, $body);
|
||||
return true;
|
||||
}))
|
||||
->with(
|
||||
$this->callback(function (StreamInterface $stream) use ($fileContents, $fileName) {
|
||||
$stream->rewind();
|
||||
$body = $stream->getContents();
|
||||
$this->assertStringContainsString(
|
||||
'Content-Disposition: form-data; name="data"; filename="' . $fileName . '"',
|
||||
$body,
|
||||
);
|
||||
$this->assertStringContainsString($fileContents, $body);
|
||||
return true;
|
||||
})
|
||||
)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->requestMock
|
||||
@@ -307,4 +312,68 @@ final class ClientTest extends TestCase
|
||||
$result = $this->client->upload($uploadUrl, $fileContents, $fileName);
|
||||
$this->assertSame($responsePayload, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadMethodHandlesStreamResourceCorrectly(): void
|
||||
{
|
||||
$uploadUrl = 'https://upload.server/path';
|
||||
$fileContents = 'data from a stream resource';
|
||||
$fileName = 'resource.txt';
|
||||
$responsePayload = ['token' => 'token_from_stream_upload'];
|
||||
|
||||
$tmpFileHandle = tmpfile();
|
||||
fwrite($tmpFileHandle, $fileContents);
|
||||
rewind($tmpFileHandle);
|
||||
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withHeader')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withBody')->willReturn($this->requestMock);
|
||||
$this->httpClientMock->method('sendRequest')->willReturn($this->responseMock);
|
||||
$this->responseMock->method('getStatusCode')->willReturn(200);
|
||||
$this->streamMock->method('__toString')->willReturn(json_encode($responsePayload));
|
||||
|
||||
$result = $this->client->upload($uploadUrl, $tmpFileHandle, $fileName);
|
||||
|
||||
$this->assertSame($responsePayload, $result);
|
||||
fclose($tmpFileHandle);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadThrowsNetworkExceptionOnClientError(): void
|
||||
{
|
||||
$this->expectException(NetworkException::class);
|
||||
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withHeader')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withBody')->willReturn($this->requestMock);
|
||||
|
||||
$psrException = new class extends \Exception implements ClientExceptionInterface {
|
||||
};
|
||||
$this->httpClientMock
|
||||
->method('sendRequest')
|
||||
->with($this->requestMock)
|
||||
->willThrowException($psrException);
|
||||
|
||||
$this->client->upload('http://some.url', 'content', 'file.txt');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadThrowsSerializationExceptionOnInvalidJsonResponse(): void
|
||||
{
|
||||
$this->expectException(SerializationException::class);
|
||||
$this->expectExceptionMessage('Failed to decode upload server response JSON.');
|
||||
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withHeader')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withBody')->willReturn($this->requestMock);
|
||||
|
||||
$this->httpClientMock
|
||||
->method('sendRequest')
|
||||
->with($this->requestMock)
|
||||
->willReturn($this->responseMock);
|
||||
|
||||
$this->responseMock->method('getStatusCode')->willReturn(200);
|
||||
$this->streamMock->method('__toString')->willReturn('{not-a-valid-json');
|
||||
$this->client->upload('http://some.url', 'content', 'file.txt');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ use BushlanovDev\MaxMessengerBot\Models\Sender;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
@@ -44,6 +46,8 @@ use PHPUnit\Framework\TestCase;
|
||||
#[UsesClass(UploadEndpoint::class)]
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(Image::class)]
|
||||
#[UsesClass(ChatTitleChangedUpdate::class)]
|
||||
#[UsesClass(MessageChatCreatedUpdate::class)]
|
||||
final class ModelFactoryTest extends TestCase
|
||||
{
|
||||
private ModelFactory $factory;
|
||||
@@ -268,6 +272,33 @@ final class ModelFactoryTest extends TestCase
|
||||
'payload' => 'start_payload',
|
||||
'user_locale' => 'ru-RU',
|
||||
],
|
||||
[
|
||||
'update_type' => 'chat_title_changed',
|
||||
'timestamp' => 1680000000,
|
||||
'chat_id' => 12345,
|
||||
'user' => [
|
||||
'user_id' => 54321,
|
||||
'first_name' => 'John',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1679999999,
|
||||
],
|
||||
'title' => 'New Awesome Chat Title',
|
||||
],
|
||||
[
|
||||
'update_type' => 'message_chat_created',
|
||||
'timestamp' => 1683000001,
|
||||
'chat' => [
|
||||
'chat_id' => 54321,
|
||||
'type' => 'chat',
|
||||
'status' => 'active',
|
||||
'last_event_time' => 1683000001,
|
||||
'participants_count' => 1,
|
||||
'is_public' => false,
|
||||
'title' => 'Another Discussion',
|
||||
],
|
||||
'message_id' => 'mid.another.message',
|
||||
'start_payload' => null,
|
||||
],
|
||||
],
|
||||
'marker' => 12345,
|
||||
];
|
||||
@@ -276,9 +307,11 @@ final class ModelFactoryTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(UpdateList::class, $updateList);
|
||||
$this->assertSame(12345, $updateList->marker);
|
||||
$this->assertCount(2, $updateList->updates);
|
||||
$this->assertCount(4, $updateList->updates);
|
||||
$this->assertInstanceOf(MessageCreatedUpdate::class, $updateList->updates[0]);
|
||||
$this->assertInstanceOf(BotStartedUpdate::class, $updateList->updates[1]);
|
||||
$this->assertSame('start_payload', $updateList->updates[1]->payload);
|
||||
$this->assertInstanceOf(ChatTitleChangedUpdate::class, $updateList->updates[2]);
|
||||
$this->assertInstanceOf(MessageChatCreatedUpdate::class, $updateList->updates[3]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
#[CoversClass(AbstractModel::class)]
|
||||
#[CoversClass(ArrayOf::class)]
|
||||
@@ -76,6 +77,139 @@ final class AbstractModelMappingTest extends TestCase
|
||||
|
||||
$this->assertNull($result->childModels);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCorrectlyCastsInteger(): void
|
||||
{
|
||||
$result = DummyModelForMapping::fromArray(['untyped_int' => 123]);
|
||||
|
||||
$this->assertIsInt($result->untypedInt);
|
||||
$this->assertSame(123, $result->untypedInt);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCorrectlyCastsFloat(): void
|
||||
{
|
||||
$result = DummyModelForMapping::fromArray(['untyped_float' => 1.23]);
|
||||
|
||||
$this->assertIsFloat($result->untypedFloat);
|
||||
$this->assertSame(1.23, $result->untypedFloat);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCorrectlyCastsBoolean(): void
|
||||
{
|
||||
$result = DummyModelForMapping::fromArray(['untyped_bool' => true]);
|
||||
|
||||
$this->assertIsBool($result->untypedBool);
|
||||
$this->assertTrue($result->untypedBool);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itReturnsValueAsIsForUnmanagedObjectType(): void
|
||||
{
|
||||
$externalObject = new stdClass();
|
||||
$externalObject->data = 'some value';
|
||||
|
||||
$rawData = [
|
||||
'name' => 'Test With External Object',
|
||||
'external_object' => $externalObject,
|
||||
];
|
||||
|
||||
$result = ModelWithExternalObject::fromArray($rawData);
|
||||
|
||||
$this->assertInstanceOf(ModelWithExternalObject::class, $result);
|
||||
$this->assertSame($externalObject, $result->externalObject);
|
||||
$this->assertSame('some value', $result->externalObject->data);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function castArrayHandlesNonArrayValueForArrayProperty(): void
|
||||
{
|
||||
$rawData = [
|
||||
'name' => 'Test with scalar instead of array',
|
||||
'tags' => 'single_tag_value',
|
||||
];
|
||||
|
||||
$result = DummyModelForArrayCast::fromArray($rawData);
|
||||
|
||||
$this->assertInstanceOf(DummyModelForArrayCast::class, $result);
|
||||
$this->assertIsArray($result->tags);
|
||||
$this->assertSame(['single_tag_value'], $result->tags);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function castArrayReturnsArrayAsIsForUnmanagedObjectTypesInArrayOf(): void
|
||||
{
|
||||
$items = [
|
||||
(object)['id' => 1, 'name' => 'Item A'],
|
||||
(object)['id' => 2, 'name' => 'Item B'],
|
||||
];
|
||||
$rawData = [
|
||||
'name' => 'Test with unmanaged objects',
|
||||
'items' => $items,
|
||||
];
|
||||
|
||||
$result = ModelWithUnmanagedArray::fromArray($rawData);
|
||||
|
||||
$this->assertInstanceOf(ModelWithUnmanagedArray::class, $result);
|
||||
$this->assertIsArray($result->items);
|
||||
$this->assertSame($items, $result->items);
|
||||
$this->assertSame('Item A', $result->items[0]->name);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function toArraySkipsUninitializedProperties(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(DummyModelForUninitializedProperty::class);
|
||||
$instance = $reflection->newInstanceWithoutConstructor();
|
||||
|
||||
$initializedProp = $reflection->getProperty('initializedProp');
|
||||
$initializedProp->setValue($instance, 'I have a value');
|
||||
|
||||
$resultArray = $instance->toArray();
|
||||
|
||||
$expectedArray = [
|
||||
'initialized_prop' => 'I have a value',
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $resultArray);
|
||||
$this->assertArrayNotHasKey('uninitialized_prop', $resultArray);
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class DummyModelForUninitializedProperty extends AbstractModel
|
||||
{
|
||||
public string $initializedProp;
|
||||
public int $uninitializedProp;
|
||||
}
|
||||
|
||||
final readonly class DummyModelForArrayCast extends AbstractModel
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $name,
|
||||
public ?array $tags,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class ModelWithUnmanagedArray extends AbstractModel
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $name,
|
||||
#[ArrayOf(stdClass::class)]
|
||||
public ?array $items,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class ModelWithExternalObject extends AbstractModel
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $name,
|
||||
public ?stdClass $externalObject,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class DummyModelForMapping extends AbstractModel
|
||||
@@ -87,8 +221,10 @@ final readonly class DummyModelForMapping extends AbstractModel
|
||||
#[ArrayOf(DummyChildModel::class)]
|
||||
public ?array $childModels,
|
||||
public ?array $untypedArray,
|
||||
)
|
||||
{
|
||||
public ?int $untypedInt,
|
||||
public ?float $untypedFloat,
|
||||
public ?bool $untypedBool,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(ContactAttachmentRequestPayload::class)]
|
||||
final class ContactAttachmentRequestPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itConstructsWithAllPropertiesAndSerializesCorrectly(): void
|
||||
{
|
||||
$name = 'John Doe';
|
||||
$contactId = 123456;
|
||||
$vcfInfo = 'BEGIN:VCARD...END:VCARD';
|
||||
$vcfPhone = 'TEL:+79991234567';
|
||||
|
||||
$payload = new ContactAttachmentRequestPayload($name, $contactId, $vcfInfo, $vcfPhone);
|
||||
|
||||
$this->assertInstanceOf(ContactAttachmentRequestPayload::class, $payload);
|
||||
$this->assertSame($name, $payload->name);
|
||||
$this->assertSame($contactId, $payload->contactId);
|
||||
$this->assertSame($vcfInfo, $payload->vcfInfo);
|
||||
$this->assertSame($vcfPhone, $payload->vcfPhone);
|
||||
|
||||
$expectedArray = [
|
||||
'name' => $name,
|
||||
'contact_id' => $contactId,
|
||||
'vcf_info' => $vcfInfo,
|
||||
'vcf_phone' => $vcfPhone,
|
||||
];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itConstructsWithNullablePropertiesAndSerializesCorrectly(): void
|
||||
{
|
||||
$name = 'Jane Doe';
|
||||
$vcfPhone = 'TEL:+79876543210';
|
||||
|
||||
$payload = new ContactAttachmentRequestPayload(
|
||||
name: $name,
|
||||
vcfPhone: $vcfPhone,
|
||||
);
|
||||
|
||||
$this->assertSame($name, $payload->name);
|
||||
$this->assertNull($payload->contactId);
|
||||
$this->assertNull($payload->vcfInfo);
|
||||
$this->assertSame($vcfPhone, $payload->vcfPhone);
|
||||
|
||||
$expectedArray = [
|
||||
'name' => $name,
|
||||
'contact_id' => null,
|
||||
'vcf_info' => null,
|
||||
'vcf_phone' => $vcfPhone,
|
||||
];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itConstructsWithAllNullsAndSerializesCorrectly(): void
|
||||
{
|
||||
$payload = new ContactAttachmentRequestPayload();
|
||||
|
||||
$this->assertNull($payload->name);
|
||||
$this->assertNull($payload->contactId);
|
||||
$this->assertNull($payload->vcfInfo);
|
||||
$this->assertNull($payload->vcfPhone);
|
||||
|
||||
$expectedArray = [
|
||||
'name' => null,
|
||||
'contact_id' => null,
|
||||
'vcf_info' => null,
|
||||
'vcf_phone' => null,
|
||||
];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(InlineKeyboardPayload::class)]
|
||||
#[CoversClass(InlineKeyboardAttachmentRequestPayload::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(LinkButton::class)]
|
||||
final class InlineKeyboardPayloadTest extends TestCase
|
||||
@@ -25,9 +25,9 @@ final class InlineKeyboardPayloadTest extends TestCase
|
||||
$buttons = [
|
||||
[new CallbackButton('Test', 'payload')],
|
||||
];
|
||||
$payload = new InlineKeyboardPayload($buttons);
|
||||
$payload = new InlineKeyboardAttachmentRequestPayload($buttons);
|
||||
|
||||
$this->assertInstanceOf(InlineKeyboardPayload::class, $payload);
|
||||
$this->assertInstanceOf(InlineKeyboardAttachmentRequestPayload::class, $payload);
|
||||
$this->assertSame($buttons, $payload->buttons);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class InlineKeyboardPayloadTest extends TestCase
|
||||
new LinkButton('Help', 'https://example.com/help'),
|
||||
],
|
||||
];
|
||||
$payload = new InlineKeyboardPayload($buttons);
|
||||
$payload = new InlineKeyboardAttachmentRequestPayload($buttons);
|
||||
|
||||
$resultArray = $payload->toArray();
|
||||
|
||||
@@ -77,7 +77,7 @@ final class InlineKeyboardPayloadTest extends TestCase
|
||||
#[Test]
|
||||
public function toArrayHandlesEmptyButtonsArray(): void
|
||||
{
|
||||
$payload = new InlineKeyboardPayload([]);
|
||||
$payload = new InlineKeyboardAttachmentRequestPayload([]);
|
||||
$resultArray = $payload->toArray();
|
||||
|
||||
$expectedArray = [
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\LocationAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(LocationAttachmentRequestPayload::class)]
|
||||
final class LocationAttachmentRequestPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itConstructsAndSerializesCorrectly(): void
|
||||
{
|
||||
$latitude = 55.7558;
|
||||
$longitude = 37.6173;
|
||||
|
||||
$payload = new LocationAttachmentRequestPayload($latitude, $longitude);
|
||||
|
||||
$this->assertInstanceOf(LocationAttachmentRequestPayload::class, $payload);
|
||||
$this->assertSame($latitude, $payload->latitude);
|
||||
$this->assertSame($longitude, $payload->longitude);
|
||||
|
||||
$expectedArray = [
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itHandlesNegativeCoordinates(): void
|
||||
{
|
||||
$latitude = -34.6037;
|
||||
$longitude = -58.3816;
|
||||
|
||||
$payload = new LocationAttachmentRequestPayload($latitude, $longitude);
|
||||
|
||||
$this->assertSame($latitude, $payload->latitude);
|
||||
$this->assertSame($longitude, $payload->longitude);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
@@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(PhotoAttachmentPayload::class)]
|
||||
#[CoversClass(PhotoAttachmentRequestPayload::class)]
|
||||
#[UsesClass(PhotoToken::class)]
|
||||
#[UsesClass(ArrayOf::class)]
|
||||
final class PhotoAttachmentPayloadTest extends TestCase
|
||||
@@ -22,7 +22,7 @@ final class PhotoAttachmentPayloadTest extends TestCase
|
||||
#[Test]
|
||||
public function canBeCreatedWithUrlOnly(): void
|
||||
{
|
||||
$payload = new PhotoAttachmentPayload(url: 'https://example.com/photo.jpg');
|
||||
$payload = new PhotoAttachmentRequestPayload(url: 'https://example.com/photo.jpg');
|
||||
|
||||
$this->assertSame('https://example.com/photo.jpg', $payload->url);
|
||||
$this->assertNull($payload->token);
|
||||
@@ -39,7 +39,7 @@ final class PhotoAttachmentPayloadTest extends TestCase
|
||||
#[Test]
|
||||
public function canBeCreatedWithTokenOnly(): void
|
||||
{
|
||||
$payload = new PhotoAttachmentPayload(token: 'uploaded_token_abc');
|
||||
$payload = new PhotoAttachmentRequestPayload(token: 'uploaded_token_abc');
|
||||
|
||||
$this->assertSame('uploaded_token_abc', $payload->token);
|
||||
$this->assertNull($payload->url);
|
||||
@@ -60,7 +60,7 @@ final class PhotoAttachmentPayloadTest extends TestCase
|
||||
new PhotoToken('token_1'),
|
||||
new PhotoToken('token_2'),
|
||||
];
|
||||
$payload = new PhotoAttachmentPayload(photos: $photos);
|
||||
$payload = new PhotoAttachmentRequestPayload(photos: $photos);
|
||||
|
||||
$this->assertSame($photos, $payload->photos);
|
||||
$this->assertNull($payload->url);
|
||||
@@ -101,8 +101,8 @@ final class PhotoAttachmentPayloadTest extends TestCase
|
||||
?array $photos
|
||||
): void {
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Provide exactly one of "url", "token", or "photos" for PhotoAttachmentPayload.');
|
||||
$this->expectExceptionMessage('Provide exactly one of "url", "token", or "photos" for PhotoAttachmentRequestPayload.');
|
||||
|
||||
new PhotoAttachmentPayload($url, $token, $photos);
|
||||
new PhotoAttachmentRequestPayload($url, $token, $photos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(ShareAttachmentRequestPayload::class)]
|
||||
final class ShareAttachmentRequestPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedWithUrlOnly(): void
|
||||
{
|
||||
$payload = new ShareAttachmentRequestPayload(url: 'https://example.com');
|
||||
|
||||
$this->assertSame('https://example.com', $payload->url);
|
||||
$this->assertNull($payload->token);
|
||||
|
||||
$expectedArray = ['url' => 'https://example.com', 'token' => null];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedWithTokenOnly(): void
|
||||
{
|
||||
$payload = new ShareAttachmentRequestPayload(token: 'share_token_abc');
|
||||
|
||||
$this->assertSame('share_token_abc', $payload->token);
|
||||
$this->assertNull($payload->url);
|
||||
|
||||
$expectedArray = ['url' => null, 'token' => 'share_token_abc'];
|
||||
$this->assertEquals($expectedArray, $payload->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{0: ?string, 1: ?string}>
|
||||
*/
|
||||
public static function invalidPayloadProvider(): array
|
||||
{
|
||||
return [
|
||||
'both null' => [null, null],
|
||||
'both set' => ['https://a.com', 'token123'],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('invalidPayloadProvider')]
|
||||
public function constructorThrowsExceptionForInvalidArguments(?string $url, ?string $token): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Provide exactly one of "url" or "token" for ShareAttachmentRequestPayload.');
|
||||
|
||||
new ShareAttachmentRequestPayload($url, $token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\StickerAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(StickerAttachmentRequestPayload::class)]
|
||||
final class StickerAttachmentRequestPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCorrectlyHandlesCreationSerializationAndState(): void
|
||||
{
|
||||
$codeValue = 'some_code_string_123';
|
||||
$rawData = ['code' => $codeValue];
|
||||
|
||||
$stickerPayload = StickerAttachmentRequestPayload::fromArray($rawData);
|
||||
|
||||
$this->assertInstanceOf(StickerAttachmentRequestPayload::class, $stickerPayload);
|
||||
$this->assertSame($codeValue, $stickerPayload->code);
|
||||
|
||||
$serializedData = $stickerPayload->toArray();
|
||||
$this->assertEquals($rawData, $serializedData);
|
||||
|
||||
$directInstance = new StickerAttachmentRequestPayload($codeValue);
|
||||
$this->assertSame($codeValue, $directInstance->code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(UploadedInfoAttachmentRequestPayload::class)]
|
||||
final class UploadedInfoPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCorrectlyHandlesCreationSerializationAndState(): void
|
||||
{
|
||||
$tokenValue = 'some_unique_token_string_123';
|
||||
$rawData = ['token' => $tokenValue];
|
||||
|
||||
$uploadedInfoPayload = UploadedInfoAttachmentRequestPayload::fromArray($rawData);
|
||||
|
||||
$this->assertInstanceOf(UploadedInfoAttachmentRequestPayload::class, $uploadedInfoPayload);
|
||||
$this->assertSame($tokenValue, $uploadedInfoPayload->token);
|
||||
|
||||
$serializedData = $uploadedInfoPayload->toArray();
|
||||
$this->assertEquals($rawData, $serializedData);
|
||||
|
||||
$directInstance = new UploadedInfoAttachmentRequestPayload($tokenValue);
|
||||
$this->assertSame($tokenValue, $directInstance->token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AudioAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(AudioAttachmentRequest::class)]
|
||||
#[UsesClass(UploadedInfoAttachmentRequestPayload::class)]
|
||||
final class AudioAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesCorrectRequestAndSerializesToArray(): void
|
||||
{
|
||||
$token = 'some_unique_audio_upload_token_98765';
|
||||
$request = new AudioAttachmentRequest($token);
|
||||
|
||||
$this->assertInstanceOf(AudioAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Audio, $request->type);
|
||||
$this->assertInstanceOf(UploadedInfoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($token, $request->payload->token);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'audio',
|
||||
'payload' => [
|
||||
'token' => $token,
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\ContactAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(ContactAttachmentRequest::class)]
|
||||
#[UsesClass(ContactAttachmentRequestPayload::class)]
|
||||
final class ContactAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesRequestWithAllFieldsAndSerializes(): void
|
||||
{
|
||||
$request = new ContactAttachmentRequest(
|
||||
name: 'John Doe',
|
||||
contactId: 12345,
|
||||
vcfInfo: 'BEGIN:VCARD...',
|
||||
vcfPhone: 'TEL:+1234567890',
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(ContactAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Contact, $request->type);
|
||||
$this->assertInstanceOf(ContactAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame('John Doe', $request->payload->name);
|
||||
$this->assertSame(12345, $request->payload->contactId);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'contact',
|
||||
'payload' => [
|
||||
'name' => 'John Doe',
|
||||
'contact_id' => 12345,
|
||||
'vcf_info' => 'BEGIN:VCARD...',
|
||||
'vcf_phone' => 'TEL:+1234567890',
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCreatesRequestWithOnlySomeFields(): void
|
||||
{
|
||||
$request = new ContactAttachmentRequest(name: 'Jane Doe', vcfPhone: 'TEL:+9876543210');
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'contact',
|
||||
'payload' => [
|
||||
'name' => 'Jane Doe',
|
||||
'contact_id' => null,
|
||||
'vcf_info' => null,
|
||||
'vcf_phone' => 'TEL:+9876543210',
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\FileAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(FileAttachmentRequest::class)]
|
||||
#[UsesClass(UploadedInfoAttachmentRequestPayload::class)]
|
||||
final class FileAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesCorrectRequestAndSerializesToArray(): void
|
||||
{
|
||||
$token = 'some_generic_file_upload_token_112233';
|
||||
$request = new FileAttachmentRequest($token);
|
||||
|
||||
$this->assertInstanceOf(FileAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::File, $request->type);
|
||||
$this->assertInstanceOf(UploadedInfoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($token, $request->payload->token);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'file',
|
||||
'payload' => [
|
||||
'token' => $token,
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\AbstractButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\InlineKeyboardAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
@@ -18,7 +18,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(InlineKeyboardAttachmentRequest::class)]
|
||||
#[UsesClass(InlineKeyboardPayload::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequestPayload::class)]
|
||||
#[UsesClass(AbstractButton::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(LinkButton::class)]
|
||||
@@ -35,7 +35,7 @@ final class InlineKeyboardAttachmentRequestTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(InlineKeyboardAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::InlineKeyboard, $request->type);
|
||||
$this->assertInstanceOf(InlineKeyboardPayload::class, $request->payload);
|
||||
$this->assertInstanceOf(InlineKeyboardAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($buttons, $request->payload->buttons);
|
||||
|
||||
$expectedArray = [
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\LocationAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\LocationAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(LocationAttachmentRequest::class)]
|
||||
#[UsesClass(LocationAttachmentRequestPayload::class)]
|
||||
final class LocationAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesRequestWithCoordinatesAndSerializes(): void
|
||||
{
|
||||
$latitude = 55.7558;
|
||||
$longitude = 37.6173;
|
||||
|
||||
$request = new LocationAttachmentRequest($latitude, $longitude);
|
||||
|
||||
$this->assertInstanceOf(LocationAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Location, $request->type);
|
||||
$this->assertInstanceOf(LocationAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($latitude, $request->payload->latitude);
|
||||
$this->assertSame($longitude, $request->payload->longitude);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'location',
|
||||
'payload' => [
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use InvalidArgumentException;
|
||||
@@ -16,7 +16,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(PhotoAttachmentRequest::class)]
|
||||
#[UsesClass(PhotoAttachmentPayload::class)]
|
||||
#[UsesClass(PhotoAttachmentRequestPayload::class)]
|
||||
#[UsesClass(PhotoToken::class)]
|
||||
final class PhotoAttachmentRequestTest extends TestCase
|
||||
{
|
||||
@@ -28,7 +28,7 @@ final class PhotoAttachmentRequestTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(PhotoAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Image, $request->type);
|
||||
$this->assertInstanceOf(PhotoAttachmentPayload::class, $request->payload);
|
||||
$this->assertInstanceOf(PhotoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($url, $request->payload->url);
|
||||
$this->assertNull($request->payload->token);
|
||||
$this->assertNull($request->payload->photos);
|
||||
@@ -52,7 +52,7 @@ final class PhotoAttachmentRequestTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(PhotoAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Image, $request->type);
|
||||
$this->assertInstanceOf(PhotoAttachmentPayload::class, $request->payload);
|
||||
$this->assertInstanceOf(PhotoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($token, $request->payload->token);
|
||||
$this->assertNull($request->payload->url);
|
||||
$this->assertNull($request->payload->photos);
|
||||
@@ -79,7 +79,7 @@ final class PhotoAttachmentRequestTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(PhotoAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Image, $request->type);
|
||||
$this->assertInstanceOf(PhotoAttachmentPayload::class, $request->payload);
|
||||
$this->assertInstanceOf(PhotoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($photos, $request->payload->photos);
|
||||
$this->assertNull($request->payload->url);
|
||||
$this->assertNull($request->payload->token);
|
||||
@@ -119,8 +119,8 @@ final class PhotoAttachmentRequestTest extends TestCase
|
||||
?array $photos
|
||||
): void {
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Provide exactly one of "url", "token", or "photos" for PhotoAttachmentPayload.');
|
||||
$this->expectExceptionMessage('Provide exactly one of "url", "token", or "photos" for PhotoAttachmentRequestPayload.');
|
||||
|
||||
new PhotoAttachmentPayload($url, $token, $photos);
|
||||
new PhotoAttachmentRequestPayload($url, $token, $photos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\ShareAttachmentRequest;
|
||||
use InvalidArgumentException;
|
||||
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(ShareAttachmentRequest::class)]
|
||||
#[UsesClass(ShareAttachmentRequestPayload::class)]
|
||||
final class ShareAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function fromUrlCreatesCorrectRequestAndSerializes(): void
|
||||
{
|
||||
$request = ShareAttachmentRequest::fromUrl('https://dev.max.ru');
|
||||
$this->assertSame(AttachmentType::Share, $request->type);
|
||||
$this->assertSame('https://dev.max.ru', $request->payload->url);
|
||||
$this->assertNull($request->payload->token);
|
||||
|
||||
$expected = ['type' => 'share', 'payload' => ['url' => 'https://dev.max.ru', 'token' => null]];
|
||||
$this->assertEquals($expected, $request->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function fromTokenCreatesCorrectRequestAndSerializes(): void
|
||||
{
|
||||
$request = ShareAttachmentRequest::fromToken('share_token_123');
|
||||
$this->assertSame(AttachmentType::Share, $request->type);
|
||||
$this->assertSame('share_token_123', $request->payload->token);
|
||||
$this->assertNull($request->payload->url);
|
||||
|
||||
$expected = ['type' => 'share', 'payload' => ['token' => 'share_token_123', 'url' => null]];
|
||||
$this->assertEquals($expected, $request->toArray());
|
||||
}
|
||||
|
||||
/** @return array<string, array{0: ?string, 1: ?string}> */
|
||||
public static function invalidPayloadProvider(): array
|
||||
{
|
||||
return [
|
||||
'both null' => [null, null],
|
||||
'both set' => ['https://a.com', 'token123'],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('invalidPayloadProvider')]
|
||||
public function payloadThrowsExceptionForInvalidArguments(?string $url, ?string $token): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Provide exactly one of "url" or "token" for ShareAttachmentRequestPayload.');
|
||||
new ShareAttachmentRequestPayload($url, $token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\StickerAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\StickerAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(StickerAttachmentRequest::class)]
|
||||
#[UsesClass(StickerAttachmentRequestPayload::class)]
|
||||
final class StickerAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesCorrectRequestAndSerializesToArray(): void
|
||||
{
|
||||
$stickerCode = 'my_awesome_sticker_code';
|
||||
$request = new StickerAttachmentRequest($stickerCode);
|
||||
|
||||
$this->assertInstanceOf(StickerAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Sticker, $request->type);
|
||||
$this->assertInstanceOf(StickerAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($stickerCode, $request->payload->code);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'sticker',
|
||||
'payload' => [
|
||||
'code' => $stickerCode,
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\UploadedInfoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(VideoAttachmentRequest::class)]
|
||||
#[UsesClass(UploadedInfoAttachmentRequestPayload::class)]
|
||||
final class VideoAttachmentRequestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itCreatesCorrectRequestAndSerializesToArray(): void
|
||||
{
|
||||
$token = 'some_unique_video_upload_token_54321';
|
||||
|
||||
$request = new VideoAttachmentRequest($token);
|
||||
|
||||
$this->assertInstanceOf(VideoAttachmentRequest::class, $request);
|
||||
$this->assertSame(AttachmentType::Video, $request->type);
|
||||
$this->assertInstanceOf(UploadedInfoAttachmentRequestPayload::class, $request->payload);
|
||||
$this->assertSame($token, $request->payload->token);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'video',
|
||||
'payload' => [
|
||||
'token' => $token,
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $request->toArray());
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,11 @@ use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use LogicException;
|
||||
use phpmock\phpunit\PHPMock;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
@@ -30,6 +34,8 @@ use PHPUnit\Framework\TestCase;
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
final class WebhookHandlerTest extends TestCase
|
||||
{
|
||||
use PHPMock;
|
||||
|
||||
private MockObject&Api $apiMock;
|
||||
private MockObject&ModelFactory $modelFactoryMock;
|
||||
private const string SECRET = 'my-super-secret-key';
|
||||
@@ -225,4 +231,30 @@ final class WebhookHandlerTest extends TestCase
|
||||
|
||||
$this->assertSame($expectedUpdate, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWithoutRequestWhenGuzzleIsPresent(): void
|
||||
{
|
||||
$this->expectException(SerializationException::class);
|
||||
$this->expectExceptionMessage('Webhook body is empty.');
|
||||
|
||||
$webhookHandler = new WebhookHandler($this->apiMock, $this->modelFactoryMock);
|
||||
$webhookHandler->handle(null);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[RunInSeparateProcess]
|
||||
#[PreserveGlobalState(false)]
|
||||
public function handleWithoutRequestWhenGuzzleIsMissing(): void
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessageMatches('/No ServerRequest was provided and "guzzlehttp\/psr7" is not found/');
|
||||
|
||||
$classExistsMock = $this->getFunctionMock('BushlanovDev\MaxMessengerBot', 'class_exists');
|
||||
|
||||
$classExistsMock->expects($this->once())->with('GuzzleHttp\Psr7\ServerRequest')->willReturn(false);
|
||||
|
||||
$webhookHandler = new WebhookHandler($this->apiMock, $this->modelFactoryMock);
|
||||
$webhookHandler->handle(null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user