mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-19 20:03:24 +00:00
Laravel support tests
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\PollingStartCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Console\Application as ConsoleApplication;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
#[CoversClass(PollingStartCommand::class)]
|
||||
#[UsesClass(MaxBotManager::class)]
|
||||
final class PollingStartCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&MaxBotManager $botManagerMock;
|
||||
private PollingStartCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->botManagerMock = $this->createMock(MaxBotManager::class);
|
||||
$this->container->instance(MaxBotManager::class, $this->botManagerMock);
|
||||
$this->container->alias(MaxBotManager::class, 'maxbot.manager');
|
||||
|
||||
$this->command = new PollingStartCommand();
|
||||
$this->command->setLaravel($this->container);
|
||||
|
||||
$application = new ConsoleApplication();
|
||||
$application->add($this->command);
|
||||
$commandInApp = $application->find('maxbot:polling:start');
|
||||
$this->tester = new CommandTester($commandInApp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleSuccessfullyCallsManagerWithCustomTimeout(): void
|
||||
{
|
||||
$timeout = 60;
|
||||
|
||||
$this->botManagerMock
|
||||
->expects($this->once())
|
||||
->method('startLongPolling')
|
||||
->with($timeout);
|
||||
|
||||
$this->tester->execute(['--timeout' => $timeout]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString("Starting long polling with a timeout of $timeout seconds...", $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleSuccessfullyUsesDefaultTimeout(): void
|
||||
{
|
||||
$defaultTimeout = 90;
|
||||
|
||||
$this->botManagerMock
|
||||
->expects($this->once())
|
||||
->method('startLongPolling')
|
||||
->with($defaultTimeout);
|
||||
|
||||
$this->tester->execute([]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString(
|
||||
"Starting long polling with a timeout of $defaultTimeout seconds...",
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCatchesExceptionAndLogsError(): void
|
||||
{
|
||||
$exceptionMessage = 'Something went wrong';
|
||||
$exception = new \RuntimeException($exceptionMessage);
|
||||
|
||||
$this->botManagerMock
|
||||
->expects($this->once())
|
||||
->method('startLongPolling')
|
||||
->willThrowException($exception);
|
||||
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with(
|
||||
"Long polling failed to start or crashed: $exceptionMessage",
|
||||
['exception' => $exception],
|
||||
);
|
||||
|
||||
$statusCode = $this->tester->execute([]);
|
||||
|
||||
$this->assertSame(1, $statusCode, 'Command should return a failure exit code.');
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString("❌ Long polling failed: $exceptionMessage", $output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use PHPUnit\Framework\TestCase as TestCaseOriginal;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
abstract class TestCase extends TestCaseOriginal
|
||||
{
|
||||
protected Container $container;
|
||||
protected CommandTester $tester;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->container = new TestApplicationContainer();
|
||||
Container::setInstance($this->container);
|
||||
Facade::setFacadeApplication($this->container);
|
||||
|
||||
$loggerMock = $this->createMock(LoggerInterface::class);
|
||||
$this->container->instance('log', $loggerMock);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Container::setInstance(null);
|
||||
Facade::clearResolvedInstances();
|
||||
|
||||
if (class_exists(\Mockery::class)) {
|
||||
\Mockery::close();
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
class TestApplicationContainer extends Container
|
||||
{
|
||||
public function runningUnitTests(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookListCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Console\Application as ConsoleApplication;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
#[CoversClass(WebhookListCommand::class)]
|
||||
#[UsesClass(Api::class)]
|
||||
#[UsesClass(Subscription::class)]
|
||||
final class WebhookListCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&Api $apiMock;
|
||||
private WebhookListCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->apiMock = $this->createMock(Api::class);
|
||||
$this->container->instance(Api::class, $this->apiMock);
|
||||
|
||||
$this->command = new WebhookListCommand();
|
||||
$this->command->setLaravel($this->container);
|
||||
|
||||
$application = new ConsoleApplication();
|
||||
$application->add($this->command);
|
||||
$commandInApp = $application->find('maxbot:webhook:list');
|
||||
$this->tester = new CommandTester($commandInApp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleDisplaysTableWithActiveSubscriptions(): void
|
||||
{
|
||||
$timestamp = 1678886400; // 2023-03-15 13:20:00 UTC
|
||||
$subscriptions = [
|
||||
new Subscription(
|
||||
'https://example.com/hook1',
|
||||
$timestamp,
|
||||
[UpdateType::MessageCreated, UpdateType::BotStarted],
|
||||
'0.0.6'
|
||||
),
|
||||
new Subscription(
|
||||
'https://example.com/hook2',
|
||||
$timestamp + 3600,
|
||||
null, // Should be rendered as 'all'
|
||||
'0.0.6'
|
||||
),
|
||||
];
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('getSubscriptions')
|
||||
->willReturn($subscriptions);
|
||||
|
||||
$this->tester->execute([]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
|
||||
$this->assertStringContainsString('https://example.com/hook1', $output);
|
||||
$this->assertStringContainsString('https://example.com/hook2', $output);
|
||||
$this->assertStringContainsString('message_created, bot_started', $output);
|
||||
$this->assertStringContainsString('all', $output);
|
||||
$this->assertStringContainsString(date('Y-m-d H:i:s', $timestamp), $output);
|
||||
$this->assertStringContainsString(date('Y-m-d H:i:s', $timestamp + 3600), $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleDisplaysMessageWhenNoSubscriptionsExist(): void
|
||||
{
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('getSubscriptions')
|
||||
->willReturn([]);
|
||||
|
||||
$this->tester->execute([]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('No active webhook subscriptions found.', $output);
|
||||
$this->assertStringNotContainsString('URL', $output, 'Table headers should not be displayed.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCatchesExceptionAndLogsError(): void
|
||||
{
|
||||
$exceptionMessage = 'API is down';
|
||||
$exception = new \RuntimeException($exceptionMessage);
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('getSubscriptions')
|
||||
->willThrowException($exception);
|
||||
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with("Webhook list error: $exceptionMessage", ['exception' => $exception]);
|
||||
|
||||
$statusCode = $this->tester->execute([]);
|
||||
|
||||
$this->assertSame(1, $statusCode, 'Command should return a failure exit code.');
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString("❌ Webhook list error: $exceptionMessage", $output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookSubscribeCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use Illuminate\Contracts\Config\Repository as Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Console\Application as ConsoleApplication;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
#[CoversClass(WebhookSubscribeCommand::class)]
|
||||
#[UsesClass(Api::class)]
|
||||
#[UsesClass(Result::class)]
|
||||
final class WebhookSubscribeCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&Api $apiMock;
|
||||
private MockObject&Config $configMock;
|
||||
private WebhookSubscribeCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->apiMock = $this->createMock(Api::class);
|
||||
$this->configMock = $this->createMock(Config::class);
|
||||
$this->container->instance(Api::class, $this->apiMock);
|
||||
$this->container->instance(Config::class, $this->configMock);
|
||||
|
||||
$this->command = new WebhookSubscribeCommand();
|
||||
$this->command->setLaravel($this->container);
|
||||
|
||||
$application = new ConsoleApplication();
|
||||
$application->add($this->command);
|
||||
$commandInApp = $application->find('maxbot:webhook:subscribe');
|
||||
$this->tester = new CommandTester($commandInApp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleSuccessfullySubscribesWithAllOptions(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$secret = 'my-super-secret';
|
||||
$types = ['message_created', 'bot_started'];
|
||||
$expectedUpdateTypes = [UpdateType::MessageCreated, UpdateType::BotStarted];
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('subscribe')
|
||||
->with($url, $secret, $expectedUpdateTypes)
|
||||
->willReturn(new Result(true, null));
|
||||
|
||||
$this->tester->execute([
|
||||
'url' => $url,
|
||||
'--secret' => $secret,
|
||||
'--types' => $types,
|
||||
]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('✅ Successfully subscribed to webhook!', $output);
|
||||
$this->assertStringContainsString("URL: $url", $output);
|
||||
$this->assertStringContainsString("Secret: ***************", $output);
|
||||
$this->assertStringContainsString("Update types: message_created, bot_started", $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleUsesSecretFromConfigWhenOptionIsNotProvided(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$configSecret = 'secret-from-config';
|
||||
|
||||
$this->configMock
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with('maxbot.webhook_secret')
|
||||
->willReturn($configSecret);
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('subscribe')
|
||||
->with($url, $configSecret, null)
|
||||
->willReturn(new Result(true, null));
|
||||
|
||||
$this->tester->execute(['url' => $url]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleFailsForInvalidUrl(): void
|
||||
{
|
||||
$this->apiMock->expects($this->never())->method('subscribe');
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => 'not-a-valid-url']);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$this->assertStringContainsString('Invalid URL provided.', $this->tester->getDisplay());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleFailsForInvalidUpdateType(): void
|
||||
{
|
||||
$this->apiMock->expects($this->never())->method('subscribe');
|
||||
|
||||
$statusCode = $this->tester->execute([
|
||||
'url' => 'https://example.com',
|
||||
'--types' => ['message_created', 'invalid_type'],
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$this->assertStringContainsString('Invalid update type: invalid_type', $this->tester->getDisplay());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleDisplaysApiErrorMessageOnFailure(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$apiErrorMessage = 'URL is already subscribed';
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('subscribe')
|
||||
->willReturn(new Result(false, $apiErrorMessage));
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => $url]);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('❌ Failed to subscribe to webhook.', $output);
|
||||
$this->assertStringContainsString("Response: $apiErrorMessage", $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCatchesExceptionAndLogsError(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$exceptionMessage = 'Network error';
|
||||
$exception = new \RuntimeException($exceptionMessage);
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('subscribe')
|
||||
->willThrowException($exception);
|
||||
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with("Webhook subscription error: $exceptionMessage", ['exception' => $exception]);
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => $url]);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString("❌ Webhook subscription error: $exceptionMessage", $output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookUnsubscribeCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Console\Application as ConsoleApplication;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
#[CoversClass(WebhookUnsubscribeCommand::class)]
|
||||
#[UsesClass(Api::class)]
|
||||
#[UsesClass(Result::class)]
|
||||
final class WebhookUnsubscribeCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&Api $apiMock;
|
||||
private WebhookUnsubscribeCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->apiMock = $this->createMock(Api::class);
|
||||
$this->container->instance(Api::class, $this->apiMock);
|
||||
|
||||
$this->command = new WebhookUnsubscribeCommand();
|
||||
$this->command->setLaravel($this->container);
|
||||
|
||||
$application = new ConsoleApplication();
|
||||
$application->add($this->command);
|
||||
$commandInApp = $application->find('maxbot:webhook:unsubscribe');
|
||||
$this->tester = new CommandTester($commandInApp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleSuccessfullyUnsubscribesWithConfirmationFlag(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('unsubscribe')
|
||||
->with($url)
|
||||
->willReturn(new Result(true, null));
|
||||
|
||||
$this->tester->execute([
|
||||
'url' => $url,
|
||||
'--confirm' => true,
|
||||
]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('✅ Successfully unsubscribed from webhook!', $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCancelsWhenNotConfirmed(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
|
||||
$this->apiMock->expects($this->never())->method('unsubscribe');
|
||||
$this->tester->setInputs(['no']);
|
||||
$this->tester->execute(['url' => $url]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('Are you sure you want to unsubscribe', $output);
|
||||
$this->assertStringContainsString('Operation cancelled.', $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleFailsForInvalidUrl(): void
|
||||
{
|
||||
$this->apiMock->expects($this->never())->method('unsubscribe');
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => 'not-a-valid-url']);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$this->assertStringContainsString('Invalid URL provided.', $this->tester->getDisplay());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleDisplaysApiErrorMessageOnFailure(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$apiErrorMessage = 'Subscription not found';
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('unsubscribe')
|
||||
->willReturn(new Result(false, $apiErrorMessage));
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => $url, '--confirm' => true]);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('❌ Failed to unsubscribe from webhook.', $output);
|
||||
$this->assertStringContainsString("Response: $apiErrorMessage", $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCatchesExceptionAndLogsError(): void
|
||||
{
|
||||
$url = 'https://example.com/webhook';
|
||||
$exceptionMessage = 'API connection refused';
|
||||
$exception = new \RuntimeException($exceptionMessage);
|
||||
|
||||
$this->apiMock
|
||||
->expects($this->once())
|
||||
->method('unsubscribe')
|
||||
->willThrowException($exception);
|
||||
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with("Webhook unsubscribe error: $exceptionMessage", ['exception' => $exception]);
|
||||
|
||||
$statusCode = $this->tester->execute(['url' => $url, '--confirm' => true]);
|
||||
|
||||
$this->assertSame(1, $statusCode);
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString("❌ Webhook unsubscribe error: $exceptionMessage", $output);
|
||||
}
|
||||
}
|
||||
@@ -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