diff --git a/.gitattributes b/.gitattributes index 336c454..b76af3c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,7 @@ /.gitattributes export-ignore /.gitignore export-ignore /.github export-ignore +/tests export-ignore +/phpunit.xml export-ignore /psalm.xml export-ignore /.php-cs-fixer.dist.php export-ignore \ No newline at end of file diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9b289af..9d8ba5d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,3 +29,6 @@ jobs: - name: Psalm run: vendor/bin/psalm --no-cache + + - name: Tests + run: vendor/bin/phpunit diff --git a/composer.json b/composer.json index 41facb9..d7ff103 100755 --- a/composer.json +++ b/composer.json @@ -11,19 +11,31 @@ "email": "anton.z@live.com" } ], - "autoload": { - "psr-4": { - "Luzrain\\TelegramBotBundle\\": "src/" - } - }, "require": { - "php" : "^8.2", + "php": "^8.2", + "luzrain/telegram-bot-api": "^2.1.1", + "symfony/config": "^6.3", "symfony/console": "^6.3", - "symfony/framework-bundle": "^6.3", - "luzrain/telegram-bot-api": "^2.1.1" + "symfony/dependency-injection": "^6.3", + "symfony/http-kernel": "^6.3" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.20", + "phpunit/phpunit": "^10.2", + "symfony/framework-bundle": "^6.3", "vimeo/psalm": "^5.13" + }, + "autoload": { + "psr-4": { + "Luzrain\\TelegramBotBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Luzrain\\TelegramBotBundle\\Test\\": "tests" + } + }, + "config": { + "sort-packages": true } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..923dbbc --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,25 @@ + + + + + tests + + + + + + + + + + src + + + diff --git a/src/DependencyInjection/CommandCompilerPass.php b/src/DependencyInjection/CommandCompilerPass.php index fc63310..bdb3cd9 100644 --- a/src/DependencyInjection/CommandCompilerPass.php +++ b/src/DependencyInjection/CommandCompilerPass.php @@ -39,8 +39,8 @@ public function process(ContainerBuilder $container) // Sort by priority usort($controllersMap, fn (array $a, array $b) => $b['priority'] <=> $a['priority']); - foreach ($controllersMap as &$row) { - unset($row['priority']); + foreach ($controllersMap as $id => $row) { + unset($controllersMap[$id]['priority']); } $container diff --git a/src/DependencyInjection/TelegramBotExtension.php b/src/DependencyInjection/TelegramBotExtension.php index 9c163e6..60f1288 100644 --- a/src/DependencyInjection/TelegramBotExtension.php +++ b/src/DependencyInjection/TelegramBotExtension.php @@ -41,7 +41,7 @@ public function load(array $configs, ContainerBuilder $container) ; $container - ->autowire('telegram_bot.client_api', ClientApi::class) + ->register('telegram_bot.client_api', ClientApi::class) ; $container @@ -49,7 +49,6 @@ public function load(array $configs, ContainerBuilder $container) ->setArgument('$updateHandler', new Reference('telegram_bot.update_handler')) ->setArgument('$secretToken', $config['secret_token']) ->addTag('controller.service_arguments') - ->addMethodCall('setContainer', [new Reference('service_container')]) ; $container diff --git a/src/TelegramBot/Controller/WebHookController.php b/src/TelegramBot/Controller/WebHookController.php index 608765b..ec0bc95 100644 --- a/src/TelegramBot/Controller/WebHookController.php +++ b/src/TelegramBot/Controller/WebHookController.php @@ -4,11 +4,9 @@ namespace Luzrain\TelegramBotBundle\TelegramBot\Controller; -use Luzrain\TelegramBotApi\Exception\TelegramCallbackException; use Luzrain\TelegramBotApi\Exception\TelegramTypeException; use Luzrain\TelegramBotApi\Type\Update; use Luzrain\TelegramBotBundle\TelegramBot\UpdateHandler; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -16,7 +14,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; -final class WebHookController extends AbstractController +final class WebHookController { public function __construct( private UpdateHandler $updateHandler, @@ -24,10 +22,6 @@ public function __construct( ) { } - /** - * @throws TelegramTypeException - * @throws TelegramCallbackException - */ public function __invoke(Request $request): Response { if ($request->getMethod() !== 'POST') { diff --git a/tests/CommandMetadataProviderTest.php b/tests/CommandMetadataProviderTest.php new file mode 100644 index 0000000..ce143b9 --- /dev/null +++ b/tests/CommandMetadataProviderTest.php @@ -0,0 +1,44 @@ +get('telegram_bot.command_metadata_provider'); + $list = $commandMetadataProvider->gelMetadataList(); + + $this->assertCount(4, $list); + $this->assertObjectInArray(new OnCommand('/start', '', false, 0), $list); + $this->assertObjectInArray(new OnCommand('/test1', 'test1 command description', true, 0), $list); + $this->assertObjectInArray(new OnCommand('/test2', 'test2 command description', true, 0), $list); + $this->assertObjectInArray(new OnCommand('/test3', 'test3 command description', false, 0), $list); + } + + private function assertObjectInArray(object $obj, array $arra): void + { + $this->assertContains(json_encode($obj), array_map(fn ($a) => json_encode($a), $arra)); + } +} diff --git a/tests/Controller/AnyUpdateController.php b/tests/Controller/AnyUpdateController.php new file mode 100644 index 0000000..dad6c44 --- /dev/null +++ b/tests/Controller/AnyUpdateController.php @@ -0,0 +1,19 @@ +reply('Callback1 answer'); + } +} diff --git a/tests/Controller/MessageController.php b/tests/Controller/MessageController.php new file mode 100644 index 0000000..4f320b2 --- /dev/null +++ b/tests/Controller/MessageController.php @@ -0,0 +1,22 @@ +reply('You wrote: ' . $message->text); + } +} diff --git a/tests/Controller/StartCommandController.php b/tests/Controller/StartCommandController.php new file mode 100644 index 0000000..527a646 --- /dev/null +++ b/tests/Controller/StartCommandController.php @@ -0,0 +1,20 @@ +reply('Start answer'); + } +} diff --git a/tests/Controller/TestCommandController.php b/tests/Controller/TestCommandController.php new file mode 100644 index 0000000..ba2b843 --- /dev/null +++ b/tests/Controller/TestCommandController.php @@ -0,0 +1,34 @@ +reply('Test1 answer'); + } + + #[OnCommand(command: '/test2', description: 'test2 command description', publish: true)] + public function test2(): Method + { + ControllerTestHelper::$isTest2CommandCommand = true; + return $this->reply('Test2 answer'); + } + + #[OnCommand(command: '/test3', description: 'test3 command description', publish: false)] + public function test3(): Method + { + ControllerTestHelper::$isTest3CommandCommand = true; + return $this->reply('Test3 answer'); + } +} diff --git a/tests/Helper/ControllerTestHelper.php b/tests/Helper/ControllerTestHelper.php new file mode 100644 index 0000000..dc7ba3b --- /dev/null +++ b/tests/Helper/ControllerTestHelper.php @@ -0,0 +1,27 @@ +set('httpClient', $this->createMock(ClientInterface::class)); + self::$container->set('requestFactory', $this->createMock(RequestFactoryInterface::class)); + self::$container->set('streamFactory', $this->createMock(StreamFactoryInterface::class)); + } + + public function testServiceAutowiring(): void + { + $this->assertInstanceOf(BotApi::class, self::$container->get(BotApi::class)); + $this->assertInstanceOf(ClientApi::class, self::$container->get('telegram_bot.client_api')); + $this->assertInstanceOf(WebHookController::class, self::$container->get('telegram_bot.webhook_controller')); + $this->assertInstanceOf(LongPollingService::class, self::$container->get('telegram_bot.long_polling_service')); + $this->assertInstanceOf(SetWebhookCommand::class, self::$container->get('telegram_bot.set_webhook_command')); + $this->assertInstanceOf(WebhookInfoCommand::class, self::$container->get('telegram_bot.get_webhook_command')); + $this->assertInstanceOf(DeleteWebhookCommand::class, self::$container->get('telegram_bot.delete_webhook_command')); + $this->assertInstanceOf(PolllingStartCommand::class, self::$container->get('telegram_bot.polling_command')); + $this->assertInstanceOf(ButtonSetCommandsCommand::class, self::$container->get('telegram_bot.menu_button_set_commands')); + $this->assertInstanceOf(ButtonDeleteCommand::class, self::$container->get('telegram_bot.menu_button_delete')); + $this->assertInstanceOf(UpdateHandler::class, self::$container->get('telegram_bot.update_handler')); + $this->assertInstanceOf(CommandMetadataProvider::class, self::$container->get('telegram_bot.command_metadata_provider')); + } +} diff --git a/tests/TestKernel.php b/tests/TestKernel.php new file mode 100644 index 0000000..6579f85 --- /dev/null +++ b/tests/TestKernel.php @@ -0,0 +1,56 @@ +load(function(ContainerBuilder $container) { + $container->register('httpClient', ClientInterface::class); + $container->register('requestFactory', RequestFactoryInterface::class); + $container->register('streamFactory', StreamFactoryInterface::class); + + $container->loadFromExtension('framework', [ + 'test' => true, + ]); + + $container->loadFromExtension('telegram_bot', [ + 'http_client' => 'httpClient', + 'request_factory' => 'requestFactory', + 'stream_factory' => 'streamFactory', + 'api_token' => '9999999999:AAABBBCCCDDDEEEFFF', + ]); + + $container->autowire(StartCommandController::class)->setAutoconfigured(true); + $container->autowire(TestCommandController::class)->setAutoconfigured(true); + $container->autowire(MessageController::class)->setAutoconfigured(true); + $container->autowire(CallbackCommandController::class)->setAutoconfigured(true); + $container->autowire(AnyUpdateController::class)->setAutoconfigured(true); + }); + } +} diff --git a/tests/UpdateHandlerTest.php b/tests/UpdateHandlerTest.php new file mode 100644 index 0000000..5c7f609 --- /dev/null +++ b/tests/UpdateHandlerTest.php @@ -0,0 +1,121 @@ +updateHandler = self::getContainer()->get('telegram_bot.update_handler'); + } + + public function testMessageHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/message.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertInstanceOf(Method\SendMessage::class, $callbackResponse); + $this->assertSame('{"method":"sendMessage","chat_id":123456789,"text":"You wrote: test test"}', json_encode($callbackResponse)); + $this->assertFalse($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertTrue($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertFalse($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } + + public function testStartCommandHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/command1.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertInstanceOf(Method\SendMessage::class, $callbackResponse); + $this->assertSame('{"method":"sendMessage","chat_id":123456789,"text":"Start answer"}', json_encode($callbackResponse)); + $this->assertTrue($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertFalse($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertFalse($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } + + public function testTest2CommandHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/command2.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertInstanceOf(Method\SendMessage::class, $callbackResponse); + $this->assertSame('{"method":"sendMessage","chat_id":123456789,"text":"Test2 answer"}', json_encode($callbackResponse)); + $this->assertFalse($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertTrue($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertFalse($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertFalse($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } + + public function testCallbackHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/callbackQuery.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertInstanceOf(Method\SendMessage::class, $callbackResponse); + $this->assertSame('{"method":"sendMessage","chat_id":123456789,"text":"Callback1 answer"}', json_encode($callbackResponse)); + $this->assertFalse($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertFalse($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertTrue($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } + + public function testUnregisteredCallbackHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/unknownCallbackQuery.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertNull($callbackResponse); + $this->assertFalse($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertFalse($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertFalse($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } + + public function testUnregisteredEventHandle(): void + { + $controllerTestHelper = new ControllerTestHelper(); + $update = Update::fromJson(file_get_contents(__DIR__ . '/data/events/myChatMember.json')); + $callbackResponse = $this->updateHandler->handle($update); + + $this->assertNull($callbackResponse); + $this->assertFalse($controllerTestHelper::$isStartCommand, '$isStartCommand'); + $this->assertFalse($controllerTestHelper::$isTest1CommandCommand, '$isTest1CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest2CommandCommand, '$isTest2CommandCommand'); + $this->assertFalse($controllerTestHelper::$isTest3CommandCommand, '$isTest3CommandCommand'); + $this->assertFalse($controllerTestHelper::$isMessage, '$isMessage'); + $this->assertFalse($controllerTestHelper::$isCallback1, '$isCallback1'); + $this->assertTrue($controllerTestHelper::$isUpdate, '$isUpdate'); + } +} diff --git a/tests/data/events/callbackQuery.json b/tests/data/events/callbackQuery.json new file mode 100644 index 0000000..c678c18 --- /dev/null +++ b/tests/data/events/callbackQuery.json @@ -0,0 +1,51 @@ +{ + "update_id":264807335, + "callback_query":{ + "id":"608120766128219694", + "from":{ + "id":123456789, + "is_bot":false, + "first_name":"Anton", + "username":"User2384885921", + "language_code":"en" + }, + "message":{ + "message_id":1867, + "from":{ + "id":5555555555, + "is_bot":true, + "first_name":"Test Bot", + "username":"VTestBot" + }, + "chat":{ + "id":123456789, + "first_name":"Anton", + "username":"User2384885921", + "type":"private" + }, + "date":1651694976, + "edit_date":1651695030, + "text":"test text", + "reply_markup":{ + "inline_keyboard":[ + [ + { + "text":"link1", + "callback_data":"test_callback_1" + }, + { + "text":"link2", + "callback_data":"test_callback_2" + }, + { + "text":"link3", + "callback_data":"test_callback_3" + } + ] + ] + } + }, + "chat_instance":"-504452832938350178", + "data":"test_callback_1" + } +} \ No newline at end of file diff --git a/tests/data/events/command1.json b/tests/data/events/command1.json new file mode 100644 index 0000000..6c1a298 --- /dev/null +++ b/tests/data/events/command1.json @@ -0,0 +1,28 @@ +{ + "update_id": 501395674, + "message": { + "message_id": 2090, + "from": { + "id": 123456789, + "is_bot": false, + "first_name": "Anton", + "username": "User2384885921", + "language_code": "en" + }, + "chat": { + "id": 123456789, + "first_name": "Anton", + "username": "User2384885921", + "type": "private" + }, + "date": 1688318097, + "text": "\/start", + "entities": [ + { + "offset": 0, + "length": 6, + "type": "bot_command" + } + ] + } +} \ No newline at end of file diff --git a/tests/data/events/command2.json b/tests/data/events/command2.json new file mode 100644 index 0000000..b3ab476 --- /dev/null +++ b/tests/data/events/command2.json @@ -0,0 +1,28 @@ +{ + "update_id": 501395674, + "message": { + "message_id": 2090, + "from": { + "id": 123456789, + "is_bot": false, + "first_name": "Anton", + "username": "User2384885921", + "language_code": "en" + }, + "chat": { + "id": 123456789, + "first_name": "Anton", + "username": "User2384885921", + "type": "private" + }, + "date": 1688318097, + "text": "\/test2", + "entities": [ + { + "offset": 0, + "length": 6, + "type": "bot_command" + } + ] + } +} \ No newline at end of file diff --git a/tests/data/events/message.json b/tests/data/events/message.json new file mode 100644 index 0000000..532e21a --- /dev/null +++ b/tests/data/events/message.json @@ -0,0 +1,21 @@ +{ + "update_id":264807340, + "message":{ + "message_id":2166, + "from":{ + "id":123456789, + "is_bot":false, + "first_name":"Anton", + "username":"User2384885921", + "language_code":"en" + }, + "chat":{ + "id":123456789, + "first_name":"Anton", + "username":"User2384885921", + "type":"private" + }, + "date":1651946129, + "text":"test test" + } +} \ No newline at end of file diff --git a/tests/data/events/myChatMember.json b/tests/data/events/myChatMember.json new file mode 100644 index 0000000..1378fc1 --- /dev/null +++ b/tests/data/events/myChatMember.json @@ -0,0 +1,38 @@ +{ + "update_id":264807559, + "my_chat_member":{ + "chat":{ + "id":123456789, + "first_name":"Anton", + "username":"User2384885921", + "type":"private" + }, + "from":{ + "id":123456789, + "is_bot":false, + "first_name":"Anton", + "username":"User2384885921", + "language_code":"en" + }, + "date":1652557603, + "old_chat_member":{ + "user":{ + "id":5123456789, + "is_bot":true, + "first_name":"Test Bot", + "username":"TelegramApiTest" + }, + "status":"member" + }, + "new_chat_member":{ + "user":{ + "id":5123456789, + "is_bot":true, + "first_name":"Test Bot", + "username":"TelegramApiTest" + }, + "status":"kicked", + "until_date":0 + } + } +} \ No newline at end of file diff --git a/tests/data/events/unknownCallbackQuery.json b/tests/data/events/unknownCallbackQuery.json new file mode 100644 index 0000000..d97d07d --- /dev/null +++ b/tests/data/events/unknownCallbackQuery.json @@ -0,0 +1,51 @@ +{ + "update_id":264807335, + "callback_query":{ + "id":"608120766128219694", + "from":{ + "id":123456789, + "is_bot":false, + "first_name":"Anton", + "username":"User2384885921", + "language_code":"en" + }, + "message":{ + "message_id":1867, + "from":{ + "id":5555555555, + "is_bot":true, + "first_name":"Test Bot", + "username":"VTestBot" + }, + "chat":{ + "id":123456789, + "first_name":"Anton", + "username":"User2384885921", + "type":"private" + }, + "date":1651694976, + "edit_date":1651695030, + "text":"test text", + "reply_markup":{ + "inline_keyboard":[ + [ + { + "text":"link1", + "callback_data":"test_callback_1" + }, + { + "text":"link2", + "callback_data":"test_callback_2" + }, + { + "text":"link3", + "callback_data":"test_callback_3" + } + ] + ] + } + }, + "chat_instance":"-504452832938350178", + "data":"test_callback_2" + } +} \ No newline at end of file