mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-20 15:33:24 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 576f02efbd | |||
| da7a07145b | |||
| 7cdbfc0f47 | |||
| f29344d2ec | |||
| 3012272497 | |||
| 0dc10bd279 | |||
| 8419a7892d | |||
| ddc57d7ab8 | |||
| d0f044c7bd | |||
| 31f109db7e | |||
| e1375acabb | |||
| 098aa44825 | |||
| 6137b2ea6e | |||
| 122afcc7c0 | |||
| 4d56119fa2 | |||
| ae53f51854 | |||
| b87b41ff8f | |||
| 493be1a9a0 |
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ "8.3", "8.4" ]
|
||||
php-versions: [ "8.3", "8.4", "8.5" ]
|
||||
name: PHP ${{ matrix.php-versions }} Test on ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -43,19 +43,13 @@ jobs:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: xdebug
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction
|
||||
run: |
|
||||
if [[ "${{ matrix.php-versions }}" == "8.5" ]]; then
|
||||
composer install --prefer-dist --no-interaction --ignore-platform-req=php
|
||||
else
|
||||
composer install --prefer-dist --no-interaction
|
||||
fi
|
||||
|
||||
- name: PHPUnit
|
||||
run: ./vendor/bin/phpunit --configuration=phpunit.xml --coverage-text
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"analyse": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=256M",
|
||||
"analyse": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=512M",
|
||||
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes src",
|
||||
"test": "vendor/bin/phpunit",
|
||||
"test-coverage": "vendor/bin/phpunit --coverage-html coverage",
|
||||
|
||||
+201
-31
@@ -1,7 +1,6 @@
|
||||
- [Быстрый старт](#Быстрый-старт)
|
||||
- [Получение токена](#Получение-токена)
|
||||
- [Установка библиотеки](#Установка-библиотеки)
|
||||
- [Установка библиотеки в Laravel](#Установка-библиотеки-в-Laravel)
|
||||
- [Инициализация бота](#Инициализация-бота)
|
||||
- [Информация о боте](#Информация-о-боте)
|
||||
- `GET /me` (`getBotInfo`) - [*Получение информации о боте.*](#Получение-информации-о-боте)
|
||||
@@ -40,6 +39,18 @@
|
||||
- `GET /messages/{messageId}` (`getMessageById`) - [*Получение сообщения по ID.*](#Получение-сообщения-по-ID)
|
||||
- `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) - [*Получение детальной информации о видео.*](#Получение-детальной-информации-о-видео)
|
||||
- `POST /answers` (`answerOnCallback`) - [*Ответ на нажатие callback-кнопки.*](#Ответ-на-нажатие-callback-кнопки)
|
||||
- [Laravel](#Laravel)
|
||||
- [Регистрация пакета](#Регистрация-пакета)
|
||||
- [Настройка](#Настройка)
|
||||
- [Использование](#Использование)
|
||||
- [Artisan команды](#Artisan-команды)
|
||||
- [Подписка на Webhook](#Подписка-на-Webhook)
|
||||
- [Удаление подписки](#Удаление-подписки)
|
||||
- [Список подписок](#Список-подписок)
|
||||
- [Обработка хуков](#Обработка-хуков)
|
||||
- [Long Polling](#Long-Polling)
|
||||
- [Handler Classes](#Handler-Classes)
|
||||
- [Тестирование](#Тестирование)
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
@@ -56,36 +67,6 @@
|
||||
composer require bushlanov-dev/max-bot-api-client-php
|
||||
```
|
||||
|
||||
### Установка библиотеки в Laravel
|
||||
|
||||
Пользователи Laravel могут зарегистрировать сервис провайдер и фасад в `config/app.php`:
|
||||
|
||||
```php
|
||||
'providers' => [
|
||||
// ...
|
||||
BushlanovDev\MaxMessengerBot\Laravel\MaxBotServiceProvider::class,
|
||||
],
|
||||
// ...
|
||||
'aliases' => [
|
||||
// ...
|
||||
'MaxBot' => BushlanovDev\MaxMessengerBot\Laravel\MaxBotFacade::class,
|
||||
],
|
||||
```
|
||||
|
||||
При не необходимости опубликовать конфиг выполните следующую команду
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --provider="BushlanovDev\MaxMessengerBot\Laravel\MaxBotServiceProvider"
|
||||
```
|
||||
|
||||
Для работы вам потребуется внести следующие настройки в `.env`
|
||||
|
||||
```env
|
||||
MAXBOT_ACCESS_TOKEN=your_bot_access_token_here
|
||||
MAXBOT_WEBHOOK_SECRET=your_webhook_secret_here
|
||||
MAXBOT_LOGGING_ENABLED=true
|
||||
```
|
||||
|
||||
### Инициализация бота
|
||||
|
||||
Единственной обязательной настройкой является токен вашего бота.
|
||||
@@ -444,3 +425,192 @@ $api->answerOnCallback(
|
||||
notify: true, // Заполните это, если хотите просто отправить одноразовое уведомление пользователю (необязательно)
|
||||
);
|
||||
```
|
||||
|
||||
## Laravel
|
||||
|
||||
### Регистрация пакета
|
||||
Пакет будет автоматически обнаружен Laravel.
|
||||
Если автоматическое обнаружение отключено можно зарегистрировать сервис провайдер и фасад в `config/app.php`:
|
||||
|
||||
```php
|
||||
'providers' => [
|
||||
// ...
|
||||
BushlanovDev\MaxMessengerBot\Laravel\MaxBotServiceProvider::class,
|
||||
],
|
||||
// ...
|
||||
'aliases' => [
|
||||
// ...
|
||||
'MaxBot' => BushlanovDev\MaxMessengerBot\Laravel\MaxBotFacade::class,
|
||||
],
|
||||
```
|
||||
### Настройка
|
||||
|
||||
При не необходимости опубликовать конфиг выполните следующую команду:
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --provider="BushlanovDev\MaxMessengerBot\Laravel\MaxBotServiceProvider"
|
||||
```
|
||||
|
||||
Для работы вам потребуется добавить ваш токен в файл `.env`
|
||||
|
||||
```env
|
||||
MAXBOT_ACCESS_TOKEN=your_bot_access_token_here
|
||||
MAXBOT_WEBHOOK_SECRET=your_webhook_secret_here
|
||||
```
|
||||
|
||||
### Использование
|
||||
|
||||
Все методы бота доступны через фасад MaxBot, например:
|
||||
|
||||
```php
|
||||
use MaxBot;
|
||||
|
||||
// Отправка сообщения пользователю
|
||||
MaxBot::sendUserMessage(123456, 'Hello from Laravel!');
|
||||
|
||||
// Получение обновления
|
||||
$updates = MaxBot::getUpdates();
|
||||
```
|
||||
|
||||
### Artisan команды
|
||||
|
||||
#### Подписка на Webhook
|
||||
|
||||
```bash
|
||||
# Подписка на получение обновлений
|
||||
php artisan maxbot:webhook:subscribe https://yourapp.com/bot/webhook
|
||||
|
||||
# С верификацией
|
||||
php artisan maxbot:webhook:subscribe https://yourapp.com/bot/webhook --secret=your_secret_key
|
||||
|
||||
# Подписка только на определенные типы событий
|
||||
php artisan maxbot:webhook:subscribe https://yourapp.com/bot/webhook --types=message_created --types=message_callback
|
||||
```
|
||||
|
||||
#### Удаление подписки
|
||||
|
||||
```bash
|
||||
php artisan maxbot:webhook:unsubscribe https://yourapp.com/bot/webhook
|
||||
```
|
||||
|
||||
#### Список подписок
|
||||
|
||||
```bash
|
||||
php artisan maxbot:webhook:list
|
||||
```
|
||||
|
||||
### Обработка хуков
|
||||
|
||||
Создайте контроллер для обработчика:
|
||||
|
||||
```php
|
||||
use Illuminate\Http\Request;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
|
||||
class WebhookController extends Controller
|
||||
{
|
||||
public function handle(Request $request, MaxBotManager $botManager)
|
||||
{
|
||||
// Обработчик сообщений
|
||||
$botManager->onMessageCreated(function (MessageCreatedUpdate $update) {
|
||||
$message = $update->message;
|
||||
// ...
|
||||
});
|
||||
|
||||
// Обработчик команды /start
|
||||
$botManager->onCommand('start', function (MessageCreatedUpdate $update) {
|
||||
// ...
|
||||
});
|
||||
|
||||
// Using Laravel container bindings
|
||||
$botManager->onMessageCreated(MessageHandler::class);
|
||||
$botManager->onCommand('help', HelpCommandHandler::class);
|
||||
|
||||
return $botManager->handleWebhook($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Добавьте маршрут в `routes/web.php`:
|
||||
|
||||
```php
|
||||
Route::post('/bot/webhook', [WebhookController::class, 'handle']);
|
||||
```
|
||||
|
||||
### Long Polling
|
||||
|
||||
Создайте artisan команду для получения long polling обновлений:
|
||||
|
||||
```php
|
||||
use Illuminate\Console\Command;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
|
||||
class BotPollingCommand extends Command
|
||||
{
|
||||
protected $signature = 'bot:polling';
|
||||
|
||||
protected $description = 'Start bot long polling';
|
||||
|
||||
public function handle(MaxBotManager $botManager)
|
||||
{
|
||||
$this->info('Starting bot polling...');
|
||||
$botManager->startLongPolling();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Handler Classes
|
||||
|
||||
Примеры классов обработчиков обновлений:
|
||||
|
||||
```php
|
||||
class MessageHandler
|
||||
{
|
||||
public function handle(MessageCreatedUpdate $update)
|
||||
{
|
||||
$message = $update->message;
|
||||
$text = $message->body?->text;
|
||||
|
||||
if ($text) {
|
||||
app(Api::class)->sendMessage(
|
||||
userId: $message->sender->userId,
|
||||
text: "You said: $text",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HelpCommandHandler
|
||||
{
|
||||
public function handle(MessageCreatedUpdate $update)
|
||||
{
|
||||
app(Api::class)->sendMessage(
|
||||
userId: $update->message->sender->userId,
|
||||
text: "This is help message",
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Тестирование
|
||||
|
||||
Вы можете использовать мок API для тестирования:
|
||||
|
||||
```php
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
|
||||
class BotTest extends TestCase
|
||||
{
|
||||
public function test_bot_sends_message()
|
||||
{
|
||||
$apiMock = $this->createMock(Api::class);
|
||||
$apiMock->expects($this->once())
|
||||
->method('sendUserMessage')
|
||||
->with(123456, 'Hello!');
|
||||
|
||||
$this->app->instance(Api::class, $apiMock);
|
||||
|
||||
// Your test code here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+13
-3
@@ -1,14 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
testdox="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
executionOrder="depends,defects"
|
||||
requireCoverageMetadata="true"
|
||||
beStrictAboutCoverageMetadata="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
displayDetailsOnPhpunitDeprecations="true"
|
||||
failOnPhpunitDeprecation="true"
|
||||
failOnDeprecation="false"
|
||||
failOnPhpunitDeprecation="false"
|
||||
displayDetailsOnPhpunitDeprecations="false"
|
||||
displayDetailsOnTestsThatTriggerDeprecations="false"
|
||||
displayDetailsOnTestsThatTriggerErrors="true"
|
||||
displayDetailsOnTestsThatTriggerNotices="true"
|
||||
displayDetailsOnTestsThatTriggerWarnings="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true">
|
||||
<testsuites>
|
||||
@@ -25,4 +31,8 @@
|
||||
<directory suffix=".php">src/Exceptions</directory>
|
||||
</exclude>
|
||||
</source>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL & ~E_DEPRECATED"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
+3
-3
@@ -48,11 +48,11 @@ use RuntimeException;
|
||||
*/
|
||||
class Api
|
||||
{
|
||||
public const string LIBRARY_VERSION = '1.2.1';
|
||||
public const string LIBRARY_VERSION = '1.3.0';
|
||||
|
||||
public const string API_VERSION = '0.0.6';
|
||||
|
||||
private const string API_BASE_URL = 'https://botapi.max.ru';
|
||||
private const string API_BASE_URL = 'https://platform-api.max.ru';
|
||||
|
||||
private const string METHOD_GET = 'GET';
|
||||
private const string METHOD_POST = 'POST';
|
||||
@@ -363,7 +363,7 @@ class Api
|
||||
$this->buildNewMessageBody($text, $attachments, $format, $link, $notify),
|
||||
);
|
||||
|
||||
return $this->modelFactory->createMessage($response['message']);
|
||||
return $this->modelFactory->createMessageFromSendResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-3
@@ -60,7 +60,6 @@ final readonly class Client implements ClientApiInterface
|
||||
*/
|
||||
public function request(string $method, string $uri, array $queryParams = [], array $body = []): array
|
||||
{
|
||||
$queryParams['access_token'] = $this->accessToken;
|
||||
if (!empty($this->apiVersion)) {
|
||||
$queryParams['v'] = $this->apiVersion;
|
||||
}
|
||||
@@ -72,7 +71,9 @@ final readonly class Client implements ClientApiInterface
|
||||
]);
|
||||
|
||||
$fullUrl = $this->baseUrl . $uri . '?' . http_build_query($queryParams);
|
||||
$request = $this->requestFactory->createRequest($method, $fullUrl);
|
||||
$request = $this->requestFactory
|
||||
->createRequest($method, $fullUrl)
|
||||
->withHeader('Authorization', $this->accessToken);
|
||||
|
||||
if (!empty($body)) {
|
||||
try {
|
||||
@@ -143,6 +144,7 @@ final readonly class Client implements ClientApiInterface
|
||||
$request = $this->requestFactory
|
||||
->createRequest('POST', $uri)
|
||||
->withHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary)
|
||||
->withHeader('Authorization', $this->accessToken)
|
||||
->withBody($bodyStream);
|
||||
|
||||
try {
|
||||
@@ -198,7 +200,8 @@ final readonly class Client implements ClientApiInterface
|
||||
->withBody($chunkStream)
|
||||
->withHeader('Content-Type', 'application/octet-stream')
|
||||
->withHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"')
|
||||
->withHeader('Content-Range', "bytes {$startByte}-{$endByte}/{$fileSize}");
|
||||
->withHeader('Content-Range', "bytes {$startByte}-{$endByte}/{$fileSize}")
|
||||
->withHeader('Authorization', $this->accessToken);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
|
||||
+30
-1
@@ -128,6 +128,33 @@ class ModelFactory
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Message from the specific response structure of the sendMessage endpoint.
|
||||
*
|
||||
* @param array<string, mixed> $data The raw response from the client.
|
||||
*
|
||||
* @return Message
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createMessageFromSendResponse(array $data): Message
|
||||
{
|
||||
$messageData = $data['message'];
|
||||
|
||||
$topLevelData = [
|
||||
'chat_id' => $data['chat_id'] ?? null,
|
||||
'recipient_id' => $data['recipient_id'] ?? null,
|
||||
'message_id' => $data['message_id'] ?? null,
|
||||
];
|
||||
$messageData = array_merge($messageData, array_filter($topLevelData, fn($value) => $value !== null));
|
||||
|
||||
if (isset($messageData['message']) && is_array($messageData['message'])) {
|
||||
$messageData['body'] = $messageData['message'];
|
||||
unset($messageData['message']);
|
||||
}
|
||||
|
||||
return $this->createMessage($messageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message.
|
||||
*
|
||||
@@ -267,7 +294,9 @@ class ModelFactory
|
||||
InlineButtonType::RequestGeoLocation => RequestGeoLocationButton::fromArray($data),
|
||||
InlineButtonType::Chat => ChatButton::fromArray($data),
|
||||
InlineButtonType::OpenApp => OpenAppButton::fromArray($data),
|
||||
default => throw new LogicException('Unknown or unsupported inline button type: ' . ($data['type'] ?? 'none')),
|
||||
default => throw new LogicException(
|
||||
'Unknown or unsupported inline button type: ' . ($data['type'] ?? 'none')
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ final readonly class Message extends AbstractModel
|
||||
* @param string|null $url Message public URL. Can be null for dialogs or non-public chats/channels.
|
||||
* @param LinkedMessage|null $link Forwarded or replied message.
|
||||
* @param MessageStat|null $stat Message statistics. Available only for channels.
|
||||
* @param int|null $chatId Chat identifier.
|
||||
* @param int|null $recipientId User identifier, if message was sent to user.
|
||||
* @param string|null $messageId Unique identifier of message.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $timestamp,
|
||||
@@ -26,6 +29,9 @@ final readonly class Message extends AbstractModel
|
||||
public ?string $url,
|
||||
public ?LinkedMessage $link,
|
||||
public ?MessageStat $stat,
|
||||
public ?int $chatId = null,
|
||||
public ?int $recipientId = null,
|
||||
public ?string $messageId = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
+52
-25
@@ -336,7 +336,7 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.456.xyz', 'seq' => 101, 'text' => $text],
|
||||
'message' => ['mid' => 'mid.456.xyz', 'seq' => 101, 'text' => $text],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => 123, 'chat_id' => null],
|
||||
'sender' => [
|
||||
'user_id' => 123,
|
||||
@@ -348,9 +348,18 @@ final class ApiTest extends TestCase
|
||||
],
|
||||
'url' => 'https://max.ru/message/123',
|
||||
],
|
||||
'chat_id' => 20414985,
|
||||
'recipient_id' => 4328369,
|
||||
'message_id' => 'mid.456.xyz',
|
||||
];
|
||||
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$finalMessageData['chat_id'] = $apiResponse['chat_id'];
|
||||
$finalMessageData['recipient_id'] = $apiResponse['recipient_id'];
|
||||
$finalMessageData['message_id'] = $apiResponse['message_id'];
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
@@ -360,8 +369,8 @@ final class ApiTest extends TestCase
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(
|
||||
@@ -416,12 +425,17 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.test.123', 'seq' => 1, 'text' => $text],
|
||||
'message' => ['mid' => 'mid.test.123', 'seq' => 1, 'text' => $text],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => 123, 'chat_id' => null],
|
||||
]
|
||||
],
|
||||
'message_id' => 'mid.test.123',
|
||||
];
|
||||
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$finalMessageData['message_id'] = $apiResponse['message_id'];
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
@@ -436,8 +450,8 @@ final class ApiTest extends TestCase
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(
|
||||
@@ -803,11 +817,15 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.sticker.1', 'seq' => 10],
|
||||
'message' => ['mid' => 'mid.sticker.1', 'seq' => 10],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
@@ -815,8 +833,8 @@ final class ApiTest extends TestCase
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$stickerRequest]);
|
||||
@@ -849,11 +867,14 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.contact.1', 'seq' => 11],
|
||||
'message' => ['mid' => 'mid.contact.1', 'seq' => 11],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
@@ -861,8 +882,8 @@ final class ApiTest extends TestCase
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$contactRequest]);
|
||||
@@ -895,11 +916,14 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.location.1', 'seq' => 12],
|
||||
'message' => ['mid' => 'mid.location.1', 'seq' => 12],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
@@ -907,8 +931,8 @@ final class ApiTest extends TestCase
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$locationRequest]);
|
||||
@@ -940,11 +964,14 @@ final class ApiTest extends TestCase
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'timestamp' => time(),
|
||||
'body' => ['mid' => 'mid.share.1', 'seq' => 13],
|
||||
'message' => ['mid' => 'mid.share.1', 'seq' => 13],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => $chatId],
|
||||
]
|
||||
];
|
||||
$expectedMessageObject = Message::fromArray($apiResponse['message']);
|
||||
$finalMessageData = $apiResponse['message'];
|
||||
$finalMessageData['body'] = $finalMessageData['message'];
|
||||
unset($finalMessageData['message']);
|
||||
$expectedMessageObject = Message::fromArray($finalMessageData);
|
||||
|
||||
$this->clientMock->expects($this->once())
|
||||
->method('request')
|
||||
@@ -952,8 +979,8 @@ final class ApiTest extends TestCase
|
||||
->willReturn($apiResponse);
|
||||
|
||||
$this->modelFactoryMock->expects($this->once())
|
||||
->method('createMessage')
|
||||
->with($apiResponse['message'])
|
||||
->method('createMessageFromSendResponse')
|
||||
->with($apiResponse)
|
||||
->willReturn($expectedMessageObject);
|
||||
|
||||
$result = $this->api->sendMessage(chatId: $chatId, attachments: [$shareRequest]);
|
||||
|
||||
+84
-15
@@ -35,7 +35,7 @@ final class ClientTest extends TestCase
|
||||
{
|
||||
private const string FAKE_TOKEN = '12345:abcdef';
|
||||
private const string API_VERSION = '0.0.6';
|
||||
private const string API_BASE_URL = 'https://botapi.max.ru';
|
||||
private const string API_BASE_URL = 'https://platform-api.max.ru';
|
||||
|
||||
private MockObject&ClientInterface $httpClientMock;
|
||||
private MockObject&RequestFactoryInterface $requestFactoryMock;
|
||||
@@ -96,7 +96,6 @@ final class ClientTest extends TestCase
|
||||
{
|
||||
$uri = '/me';
|
||||
$expectedUrl = self::API_BASE_URL . $uri . '?' . http_build_query([
|
||||
'access_token' => self::FAKE_TOKEN,
|
||||
'v' => self::API_VERSION,
|
||||
]);
|
||||
$responsePayload = ['id' => 987, 'name' => 'TestBot'];
|
||||
@@ -108,6 +107,12 @@ final class ClientTest extends TestCase
|
||||
->with('GET', $expectedUrl)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->method('withHeader')
|
||||
->with('Authorization', self::FAKE_TOKEN)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->httpClientMock
|
||||
->expects($this->once())
|
||||
->method('sendRequest')
|
||||
@@ -138,8 +143,7 @@ final class ClientTest extends TestCase
|
||||
];
|
||||
$responsePayload = ['success' => true];
|
||||
$expectedUrl = self::API_BASE_URL . $uri . '?' . http_build_query([
|
||||
'access_token' => self::FAKE_TOKEN,
|
||||
'v' => self::API_VERSION
|
||||
'v' => self::API_VERSION,
|
||||
]);
|
||||
|
||||
$this->requestFactoryMock
|
||||
@@ -157,11 +161,23 @@ final class ClientTest extends TestCase
|
||||
}))
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$headerCallCount = 0;
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->expects($this->exactly(2))
|
||||
->method('withHeader')
|
||||
->with('Content-Type', 'application/json; charset=utf-8')
|
||||
->willReturn($this->requestMock);
|
||||
->willReturnCallback(function (string $header, string $value) use (&$headerCallCount) {
|
||||
if ($headerCallCount === 0) {
|
||||
$this->assertSame('Authorization', $header);
|
||||
$this->assertSame(self::FAKE_TOKEN, $value);
|
||||
} elseif ($headerCallCount === 1) {
|
||||
$this->assertSame('Content-Type', $header);
|
||||
$this->assertSame('application/json; charset=utf-8', $value);
|
||||
}
|
||||
|
||||
$headerCallCount++;
|
||||
|
||||
return $this->requestMock;
|
||||
});
|
||||
|
||||
$this->responseMock->method('getStatusCode')->willReturn(200);
|
||||
$this->streamMock->method('__toString')->willReturn(json_encode($responsePayload));
|
||||
@@ -298,11 +314,23 @@ final class ClientTest extends TestCase
|
||||
)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$headerCallCount = 0;
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->expects($this->exactly(2))
|
||||
->method('withHeader')
|
||||
->with($this->stringStartsWith('Content-Type'), $this->stringStartsWith('multipart/form-data'))
|
||||
->willReturn($this->requestMock);
|
||||
->willReturnCallback(function (string $header, string $value) use (&$headerCallCount) {
|
||||
if ($headerCallCount === 0) {
|
||||
$this->assertSame('Content-Type', $header);
|
||||
$this->assertStringStartsWith('multipart/form-data; boundary=', $value);
|
||||
} elseif ($headerCallCount === 1) {
|
||||
$this->assertSame('Authorization', $header);
|
||||
$this->assertSame(self::FAKE_TOKEN, $value);
|
||||
}
|
||||
|
||||
$headerCallCount++;
|
||||
|
||||
return $this->requestMock;
|
||||
});
|
||||
|
||||
$this->requestFactoryMock
|
||||
->expects($this->once())
|
||||
@@ -330,9 +358,26 @@ final class ClientTest extends TestCase
|
||||
rewind($tmpFileHandle);
|
||||
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withHeader')->willReturn($this->requestMock);
|
||||
|
||||
$headerCallCount = 0;
|
||||
$this->requestMock
|
||||
->expects($this->exactly(2))
|
||||
->method('withHeader')
|
||||
->willReturnCallback(function (string $header, string $value) use (&$headerCallCount) {
|
||||
if ($headerCallCount === 0) {
|
||||
$this->assertSame('Content-Type', $header);
|
||||
$this->assertStringStartsWith('multipart/form-data; boundary=', $value);
|
||||
} elseif ($headerCallCount === 1) {
|
||||
$this->assertSame('Authorization', $header);
|
||||
$this->assertSame(self::FAKE_TOKEN, $value);
|
||||
}
|
||||
|
||||
$headerCallCount++;
|
||||
|
||||
return $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));
|
||||
|
||||
@@ -466,7 +511,30 @@ final class ClientTest extends TestCase
|
||||
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withBody')->willReturnSelf();
|
||||
$this->requestMock->method('withHeader')->willReturnSelf();
|
||||
|
||||
$headerCallCount = 0;
|
||||
$this->requestMock
|
||||
->expects($this->exactly(4))
|
||||
->method('withHeader')
|
||||
->willReturnCallback(function (string $header, string $value) use (&$headerCallCount, $fileName, $fileSize) {
|
||||
if ($headerCallCount === 0) {
|
||||
$this->assertSame('Content-Type', $header);
|
||||
$this->assertSame('application/octet-stream', $value);
|
||||
} elseif ($headerCallCount === 1) {
|
||||
$this->assertSame('Content-Disposition', $header);
|
||||
$this->assertSame('attachment; filename="' . $fileName . '"', $value);
|
||||
} elseif ($headerCallCount === 2) {
|
||||
$this->assertSame('Content-Range', $header);
|
||||
$this->assertSame("bytes 0-8/{$fileSize}", $value);
|
||||
} elseif ($headerCallCount === 3) {
|
||||
$this->assertSame('Authorization', $header);
|
||||
$this->assertSame(self::FAKE_TOKEN, $value);
|
||||
}
|
||||
|
||||
$headerCallCount++;
|
||||
|
||||
return $this->requestMock;
|
||||
});
|
||||
|
||||
$this->httpClientMock
|
||||
->expects($this->once())
|
||||
@@ -486,7 +554,8 @@ final class ClientTest extends TestCase
|
||||
#[Test]
|
||||
public function resumableUploadSuccessfullyUploadsMultipleChunks(): void
|
||||
{
|
||||
$fileContents = str_repeat('A', 3 * 1024 * 1024); // 3 MB
|
||||
$chunkSize = 1024 * 1024;
|
||||
$fileContents = str_repeat('A', 3 * $chunkSize); // 3 MB
|
||||
$fileResource = fopen('php://memory', 'w+');
|
||||
fwrite($fileResource, $fileContents);
|
||||
rewind($fileResource);
|
||||
@@ -513,7 +582,7 @@ final class ClientTest extends TestCase
|
||||
->method('__toString')
|
||||
->willReturnOnConsecutiveCalls('', '', '<retval>1</retval>');
|
||||
|
||||
$result = $this->client->resumableUpload($uploadUrl, $fileResource, $fileName, $fileSize, 1024 * 1024);
|
||||
$result = $this->client->resumableUpload($uploadUrl, $fileResource, $fileName, $fileSize, $chunkSize);
|
||||
|
||||
$this->assertSame('<retval>1</retval>', $result);
|
||||
fclose($fileResource);
|
||||
|
||||
@@ -92,6 +92,8 @@ final class MaxBotServiceProviderTest extends TestCase
|
||||
#[PreserveGlobalState(false)]
|
||||
public function itThrowsExceptionWhenGuzzleIsMissing(): void
|
||||
{
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Guzzle HTTP client is required. Please run "composer require guzzlehttp/guzzle".'
|
||||
|
||||
@@ -220,6 +220,36 @@ final class ModelFactoryTest extends TestCase
|
||||
$this->assertSame(UpdateType::MessageCreated, $subscriptions[0]->updateTypes[0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createMessageFromSendResponseCorrectlyTransformsData(): void
|
||||
{
|
||||
$apiResponse = [
|
||||
'message' => [
|
||||
'recipient' => ['chat_id' => 20414985, 'chat_type' => 'dialog', 'user_id' => 4328369],
|
||||
'timestamp' => 1760089962345,
|
||||
'sender' => [
|
||||
'user_id' => 24480184,
|
||||
'first_name' => 'Autobot',
|
||||
'is_bot' => true,
|
||||
'last_activity_time' => 1760089962354,
|
||||
],
|
||||
'message' => ['mid' => 'mid.xyz', 'seq' => 1153, 'text' => '123123'],
|
||||
],
|
||||
'chat_id' => 20414985,
|
||||
'recipient_id' => 4328369,
|
||||
'message_id' => 'mid.xyz',
|
||||
];
|
||||
|
||||
$message = $this->factory->createMessageFromSendResponse($apiResponse);
|
||||
|
||||
$this->assertInstanceOf(Message::class, $message);
|
||||
$this->assertInstanceOf(MessageBody::class, $message->body);
|
||||
$this->assertSame('mid.xyz', $message->body->mid);
|
||||
$this->assertSame(20414985, $message->chatId);
|
||||
$this->assertSame(4328369, $message->recipientId);
|
||||
$this->assertSame('mid.xyz', $message->messageId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createMessage(): void
|
||||
{
|
||||
|
||||
@@ -64,6 +64,9 @@ final class MessageTest extends TestCase
|
||||
'stat' => [
|
||||
'views' => 500,
|
||||
],
|
||||
'chat_id' => null,
|
||||
'recipient_id' => null,
|
||||
'message_id' => null,
|
||||
];
|
||||
|
||||
$message = Message::fromArray($data);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
Reference in New Issue
Block a user