175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
from types import SimpleNamespace
|
|
from unittest import mock
|
|
|
|
from django.test import TestCase, override_settings
|
|
from telebot import types
|
|
|
|
from . import interfaces
|
|
from .dispatcher import handle_update
|
|
from .interfaces import Interface, command, register, state, text
|
|
from .models import Bot, Chat, Message, User
|
|
from .testing import FakeApi, message_update
|
|
|
|
|
|
@register
|
|
class EchoInterface(Interface):
|
|
title = 'Echo'
|
|
|
|
@command('start')
|
|
@text('меню')
|
|
def menu(self, ctx):
|
|
ctx.reset()
|
|
ctx.reply('menu', keyboard=[['ask']])
|
|
|
|
@text('ask')
|
|
def ask(self, ctx):
|
|
ctx.state = 'await_name'
|
|
ctx.reply('name?')
|
|
|
|
@text('отмена', state='await_name')
|
|
def cancel_name(self, ctx):
|
|
ctx.state = ''
|
|
ctx.reply('cancelled')
|
|
|
|
@text('отмена')
|
|
def cancel(self, ctx):
|
|
ctx.reply('nothing to cancel')
|
|
|
|
@state('await_name')
|
|
def name(self, ctx):
|
|
ctx.data['name'] = ctx.text
|
|
ctx.state = ''
|
|
ctx.reply(f'hi {ctx.text}')
|
|
|
|
def fallback(self, ctx):
|
|
ctx.reply(f'echo {ctx.text}')
|
|
|
|
|
|
@register
|
|
class ChildInterface(EchoInterface):
|
|
@command('start')
|
|
def child_start(self, ctx):
|
|
ctx.reply('child')
|
|
|
|
|
|
def member_update(update_id, status):
|
|
return types.Update.de_json({
|
|
'update_id': update_id,
|
|
'my_chat_member': {
|
|
'chat': {'id': -100, 'type': 'group', 'title': 'group'},
|
|
'from': {'id': 7, 'is_bot': False, 'first_name': 'Ann'},
|
|
'date': 0,
|
|
'old_chat_member': {'user': {'id': 1, 'is_bot': True, 'first_name': 'bot'}, 'status': 'left'},
|
|
'new_chat_member': {'user': {'id': 1, 'is_bot': True, 'first_name': 'bot'}, 'status': status},
|
|
},
|
|
})
|
|
|
|
|
|
class DispatcherTests(TestCase):
|
|
def setUp(self):
|
|
self.api = FakeApi()
|
|
self.bot = Bot.objects.create(token='1:test', interface=interfaces.key(EchoInterface))
|
|
|
|
def send(self, text, update_id=1, **kwargs):
|
|
handle_update(self.api, self.bot, message_update(update_id, text, **kwargs))
|
|
return self.api.last_text
|
|
|
|
def test_command_text_state_fallback(self):
|
|
self.assertEqual(self.send('/start'), 'menu')
|
|
self.assertEqual(self.send('ask'), 'name?')
|
|
self.assertEqual(self.send('Bob'), 'hi Bob')
|
|
self.assertEqual(self.send('whatever'), 'echo whatever')
|
|
self.assertEqual(Chat.objects.get().data, {'name': 'Bob'})
|
|
|
|
def test_state_button_has_priority(self):
|
|
self.assertEqual(self.send('отмена'), 'nothing to cancel')
|
|
self.send('ask')
|
|
self.assertEqual(self.send('отмена'), 'cancelled')
|
|
self.assertEqual(Chat.objects.get().state, '')
|
|
|
|
def test_global_button_works_in_state(self):
|
|
self.send('ask')
|
|
self.assertEqual(self.send('меню'), 'menu')
|
|
|
|
def test_command_with_bot_name_and_args(self):
|
|
self.assertEqual(self.send('/START@my_bot payload'), 'menu')
|
|
|
|
def test_saves_user_chat_and_messages(self):
|
|
self.send('/start')
|
|
user = User.objects.get()
|
|
self.assertEqual((user.tg_id, user.username), (7, 'user7'))
|
|
chat = Chat.objects.get()
|
|
self.assertEqual((chat.chat_id, chat.user, chat.type), (42, user, 'private'))
|
|
self.assertEqual(
|
|
list(Message.objects.order_by('pk').values_list('direction', 'text')),
|
|
[('in', '/start'), ('out', 'menu')],
|
|
)
|
|
|
|
def test_ignores_group_messages(self):
|
|
self.assertIsNone(self.send('/start', chat_type='group'))
|
|
self.assertFalse(Message.objects.exists())
|
|
|
|
def test_interface_switch_applies_immediately(self):
|
|
self.bot.interface = interfaces.key(ChildInterface)
|
|
self.assertEqual(self.send('/start'), 'child')
|
|
self.assertEqual(self.send('меню'), 'menu')
|
|
|
|
def test_no_interface(self):
|
|
self.bot.interface = ''
|
|
self.assertIsNone(self.send('/start'))
|
|
self.assertEqual(Message.objects.count(), 1)
|
|
|
|
def test_membership_marks_chat_deleted(self):
|
|
handle_update(self.api, self.bot, member_update(1, 'member'))
|
|
self.assertIsNone(Chat.objects.get().deleted_at)
|
|
handle_update(self.api, self.bot, member_update(2, 'kicked'))
|
|
self.assertIsNotNone(Chat.objects.get().deleted_at)
|
|
self.assertEqual(Chat.objects.count(), 1)
|
|
|
|
|
|
class PublishTests(TestCase):
|
|
def setUp(self):
|
|
self.bot = Bot.objects.create(token='1:test')
|
|
self.api = mock.Mock()
|
|
self.api.get_me.return_value = SimpleNamespace(username='my_bot', first_name='My')
|
|
|
|
def publish(self):
|
|
with mock.patch.object(Bot, 'api', return_value=self.api), \
|
|
mock.patch('Telegram.models.log_out_from_cloud') as log_out:
|
|
self.bot.publish()
|
|
return log_out
|
|
|
|
@override_settings(TELEGRAM_API_URL='http://127.0.0.1:8081')
|
|
def test_logs_out_from_cloud_once(self):
|
|
self.assertEqual(self.publish().call_count, 1)
|
|
self.bot.refresh_from_db()
|
|
self.assertTrue(self.bot.cloud_logged_out)
|
|
self.assertTrue(self.bot.is_active)
|
|
self.assertEqual(self.bot.username, 'my_bot')
|
|
self.assertEqual(self.publish().call_count, 0)
|
|
|
|
@override_settings(TELEGRAM_API_URL='')
|
|
def test_cloud_api_without_log_out(self):
|
|
self.assertEqual(self.publish().call_count, 0)
|
|
self.assertFalse(self.bot.cloud_logged_out)
|
|
|
|
|
|
class InterfaceRegistryTests(TestCase):
|
|
def test_registered_interfaces_are_choices(self):
|
|
self.assertIn((interfaces.key(EchoInterface), 'Echo'), interfaces.choices())
|
|
|
|
class Unregistered(Interface):
|
|
pass
|
|
|
|
self.assertIsNone(interfaces.get(interfaces.key(Unregistered)))
|
|
|
|
|
|
class AdminTests(TestCase):
|
|
def test_bot_admin_pages(self):
|
|
from django.contrib.auth import get_user_model
|
|
admin = get_user_model().objects.create_superuser('root', 'secret-pass-123')
|
|
self.client.force_login(admin)
|
|
bot = Bot.objects.create(token='1:test')
|
|
for url in ('/admin/Telegram/bot/', '/admin/Telegram/bot/add/', f'/admin/Telegram/bot/{bot.pk}/change/'):
|
|
self.assertEqual(self.client.get(url).status_code, 200, url)
|