mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-10-11 22:38:18 +00:00
Laravel support tests
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ChatType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Recipient;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\UpdateDispatcher;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[CoversClass(MaxBotManager::class)]
|
||||
#[UsesClass(Message::class)]
|
||||
#[UsesClass(MessageBody::class)]
|
||||
#[UsesClass(Recipient::class)]
|
||||
#[UsesClass(AbstractUpdate::class)]
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
#[UsesClass(UpdateDispatcher::class)]
|
||||
#[UsesClass(WebhookHandler::class)]
|
||||
final class MaxBotManagerTest extends TestCase
|
||||
{
|
||||
private Container $container;
|
||||
private MockObject&Api $apiMock;
|
||||
private MockObject&ModelFactory $modelFactoryMock;
|
||||
private UpdateDispatcher $updateDispatcher;
|
||||
private MaxBotManager $manager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->container = new Container();
|
||||
Container::setInstance($this->container);
|
||||
Facade::setFacadeApplication($this->container);
|
||||
|
||||
$this->container->singleton('config', function ($app) {
|
||||
return $app->make(Repository::class);
|
||||
});
|
||||
|
||||
$loggerMock = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->container->instance(LoggerInterface::class, $loggerMock);
|
||||
$this->container->instance('log', $loggerMock);
|
||||
|
||||
$this->apiMock = $this->createMock(Api::class);
|
||||
$this->modelFactoryMock = $this->createMock(ModelFactory::class);
|
||||
|
||||
$this->updateDispatcher = new UpdateDispatcher($this->apiMock);
|
||||
|
||||
$this->container->instance(Api::class, $this->apiMock);
|
||||
$this->container->instance('maxbot', $this->apiMock);
|
||||
$this->container->instance(ModelFactory::class, $this->modelFactoryMock);
|
||||
$this->container->instance(UpdateDispatcher::class, $this->updateDispatcher);
|
||||
|
||||
$this->container->singleton(MaxBotManager::class, function ($app) {
|
||||
return new MaxBotManager(
|
||||
$app,
|
||||
$app->make(Api::class),
|
||||
$app->make(UpdateDispatcher::class),
|
||||
);
|
||||
});
|
||||
$this->manager = $this->container->make(MaxBotManager::class);
|
||||
|
||||
Handlers::reset();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Container::setInstance(null);
|
||||
Facade::clearResolvedInstances();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWebhookReturns200OnSuccess(): void
|
||||
{
|
||||
$webhookHandler = new WebhookHandler(
|
||||
$this->updateDispatcher,
|
||||
$this->modelFactoryMock,
|
||||
$this->container->make(\Psr\Log\LoggerInterface::class),
|
||||
null,
|
||||
);
|
||||
$this->container->instance(WebhookHandler::class, $webhookHandler);
|
||||
|
||||
$request = Request::create('/webhook', 'POST', content: '{"update_type":"message_created"}');
|
||||
|
||||
$realUpdate = new MessageCreatedUpdate(
|
||||
time(),
|
||||
$this->createMinimalMessage(),
|
||||
'ru-RU',
|
||||
);
|
||||
|
||||
$this->modelFactoryMock->method('createUpdate')->willReturn($realUpdate);
|
||||
|
||||
$wasDispatched = false;
|
||||
$this->updateDispatcher->addHandler(UpdateType::MessageCreated, function () use (&$wasDispatched) {
|
||||
$wasDispatched = true;
|
||||
});
|
||||
|
||||
$response = $this->manager->handleWebhook($request);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertTrue($wasDispatched, 'The update was not dispatched correctly.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWebhookReturns403OnSecurityException(): void
|
||||
{
|
||||
$webhookHandler = new WebhookHandler(
|
||||
$this->updateDispatcher,
|
||||
$this->modelFactoryMock,
|
||||
$this->container->make(\Psr\Log\LoggerInterface::class),
|
||||
'real-secret',
|
||||
);
|
||||
$this->container->instance(WebhookHandler::class, $webhookHandler);
|
||||
|
||||
$request = Request::create('/webhook', 'POST', content: '{}');
|
||||
$request->headers->set('X-Max-Bot-Api-Secret', 'wrong-secret');
|
||||
|
||||
$response = $this->manager->handleWebhook($request);
|
||||
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
$this->assertJsonStringEqualsJsonString('{"status":"error","message":"Forbidden"}', $response->getContent());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWebhookReturns400OnSerializationException(): void
|
||||
{
|
||||
$webhookHandler = new WebhookHandler(
|
||||
$this->updateDispatcher,
|
||||
$this->modelFactoryMock,
|
||||
$this->container->make(\Psr\Log\LoggerInterface::class),
|
||||
null
|
||||
);
|
||||
$this->container->instance(WebhookHandler::class, $webhookHandler);
|
||||
|
||||
$request = Request::create('/webhook', 'POST', content: '{invalid-json');
|
||||
|
||||
$response = $this->manager->handleWebhook($request);
|
||||
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
$this->assertJsonStringEqualsJsonString('{"status":"error","message":"Bad Request"}', $response->getContent());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWebhookReturns500OnGenericException(): void
|
||||
{
|
||||
$webhookHandler = new WebhookHandler(
|
||||
$this->updateDispatcher,
|
||||
$this->modelFactoryMock,
|
||||
$this->container->make(\Psr\Log\LoggerInterface::class),
|
||||
null
|
||||
);
|
||||
$this->container->instance(WebhookHandler::class, $webhookHandler);
|
||||
|
||||
$this->modelFactoryMock->method('createUpdate')->willThrowException(new \Exception('DB error'));
|
||||
|
||||
$request = Request::create('/webhook', 'POST', content: '{"update_type":"message_created"}');
|
||||
|
||||
$response = $this->manager->handleWebhook($request);
|
||||
|
||||
$this->assertSame(500, $response->getStatusCode());
|
||||
$this->assertJsonStringEqualsJsonString(
|
||||
'{"status":"error","message":"Internal Server Error"}',
|
||||
$response->getContent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerCanResolveCallable(): void
|
||||
{
|
||||
$wasCalled = false;
|
||||
$callable = function () use (&$wasCalled) {
|
||||
$wasCalled = true;
|
||||
};
|
||||
|
||||
$this->manager->onCommand('test', $callable);
|
||||
$dispatcher = $this->manager->getDispatcher();
|
||||
|
||||
$messageBody = new MessageBody('mid.cmd', 1, 'test', [], []);
|
||||
$message = new Message(time(), new Recipient(ChatType::Dialog, 1, null), $messageBody, null, null, null, null);
|
||||
$update = new MessageCreatedUpdate(time(), $message, null);
|
||||
|
||||
$dispatcher->dispatch($update);
|
||||
|
||||
$this->assertTrue($wasCalled, 'The resolved callable handler was not called.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerResolvesClassWithHandleMethod(): void
|
||||
{
|
||||
$this->manager->onCommand('test', TestHandlerWithHandleMethod::class);
|
||||
|
||||
$this->dispatchCommand('test');
|
||||
|
||||
$this->assertTrue(Handlers::$wasCalled, 'Handler with handle() method was not resolved and called.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerResolvesInvokableClassFromContainer(): void
|
||||
{
|
||||
$this->container->bind(TestHandlerWithInvokeMethod::class);
|
||||
|
||||
$this->manager->onCommand('test', TestHandlerWithInvokeMethod::class);
|
||||
|
||||
$this->dispatchCommand('test');
|
||||
|
||||
$this->assertTrue(Handlers::$wasCalled, 'Invokable handler was not resolved and called.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerResolvesClassAtMethodString(): void
|
||||
{
|
||||
$this->manager->onCommand('test', TestHandlerWithCustomMethod::class . '@custom');
|
||||
|
||||
$this->dispatchCommand('test');
|
||||
|
||||
$this->assertTrue(Handlers::$wasCalled, 'Handler with "Class@method" string was not resolved and called.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerResolvesBoundClassWithHandleMethod(): void
|
||||
{
|
||||
// Шаг 1: Явно регистрируем класс обработчика в контейнере.
|
||||
// Это гарантирует, что будет выбрана ветка `if ($this->container->bound($handler))`.
|
||||
$this->container->bind(TestHandlerWithHandleMethod::class);
|
||||
|
||||
// Шаг 2: Регистрируем обработчик, используя его имя класса (строку).
|
||||
$this->manager->onCommand('test', TestHandlerWithHandleMethod::class);
|
||||
|
||||
// Шаг 3: Диспетчеризуем команду, которая вызовет обработчик.
|
||||
$this->dispatchCommand('test');
|
||||
|
||||
// Шаг 4: Убеждаемся, что метод `handle` был вызван.
|
||||
$this->assertTrue(
|
||||
Handlers::$wasCalled,
|
||||
'Bound handler with handle() method was not resolved and called via the bound path.'
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerThrowsExceptionForUnresolvableString(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Unable to resolve handler: NonExistentClass');
|
||||
|
||||
$this->manager->onCommand('test', 'NonExistentClass');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function resolveHandlerThrowsExceptionForClassWithoutHandleOrInvoke(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage(
|
||||
"Handler class '" . Handlers::class . "' is not callable and doesn't have a handle method."
|
||||
);
|
||||
|
||||
$this->container->bind(Handlers::class);
|
||||
|
||||
$this->manager->onCommand('test', Handlers::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to dispatch a command to the real UpdateDispatcher.
|
||||
*/
|
||||
private function dispatchCommand(string $commandText): void
|
||||
{
|
||||
$dispatcher = $this->manager->getDispatcher();
|
||||
$messageBody = new MessageBody('mid.cmd', 1, $commandText, [], []);
|
||||
$message = new Message(time(), new Recipient(ChatType::Dialog, 1, null), $messageBody, null, null, null, null);
|
||||
$update = new MessageCreatedUpdate(time(), $message, null);
|
||||
|
||||
$dispatcher->dispatch($update);
|
||||
}
|
||||
|
||||
private function createMinimalMessage(): Message
|
||||
{
|
||||
return new Message(
|
||||
time(),
|
||||
new Recipient(ChatType::Dialog, 1, null),
|
||||
new MessageBody('mid.1', 1, 'test', [], []),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Handlers
|
||||
{
|
||||
public static bool $wasCalled = false;
|
||||
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$wasCalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHandlerWithHandleMethod
|
||||
{
|
||||
public function handle(MessageCreatedUpdate $update, Api $api): void
|
||||
{
|
||||
Handlers::$wasCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHandlerWithInvokeMethod
|
||||
{
|
||||
public function __invoke(MessageCreatedUpdate $update, Api $api): void
|
||||
{
|
||||
Handlers::$wasCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHandlerWithCustomMethod
|
||||
{
|
||||
public function custom(MessageCreatedUpdate $update, Api $api): void
|
||||
{
|
||||
Handlers::$wasCalled = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user