chore: переезд на serWebhook
This commit is contained in:
1 parent
45e7a1d22f
commit
f1414e5ace
42 files changed
+1391
-224
No files matched your search
+186
-13
@@ -1,11 +1,14 @@
|
||||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from dramatiq.middleware.time_limit import TimeLimitExceeded
|
||||
from Telegram import interfaces
|
||||
from Telegram.models import Bot, Chat, User as TelegramUser
|
||||
from Telegram.testing import Conversation, FakeApi
|
||||
|
||||
from . import fields, tasks
|
||||
from . import fields, services, tasks
|
||||
from .bot import texts
|
||||
from .bot.interface import MyPointInterface, is_proxy_url, parse_credentials
|
||||
from .models import Credentials, Point, Server
|
||||
@@ -83,7 +86,9 @@ class AddServerTests(BotTestCase):
|
||||
self.assertEqual((point.mask.address, point.mask.root_password), ('10.0.0.2', 'mask-pw'))
|
||||
self.assertEqual((point.domain, point.outbound_proxy), ('example.com', 'cf_warp'))
|
||||
self.assertEqual(point.owner, TelegramUser.objects.get())
|
||||
send.assert_called_once_with(point.pk, Chat.objects.get().pk)
|
||||
send.assert_called_once_with(point.pk, mock.ANY)
|
||||
point.refresh_from_db()
|
||||
self.assertEqual((point.task_chat, point.task_token), (Chat.objects.get(), send.call_args.args[1]))
|
||||
# сообщения с паролями удалены из чата
|
||||
self.assertEqual(len(self.api.deleted), 2)
|
||||
self.assertEqual(Chat.objects.get().state, '')
|
||||
@@ -158,7 +163,7 @@ class PointMenuTests(BotTestCase):
|
||||
self.assertIn(self.point.name, self.say(texts.NO))
|
||||
self.assertIn('Продолжить?', self.say(texts.RELEASE))
|
||||
self.assertEqual(self.say(texts.YES), texts.RELEASE_STARTED)
|
||||
send.assert_called_once_with(self.point.pk, Chat.objects.get(user__tg_id=7).pk)
|
||||
send.assert_called_once_with(self.point.pk, mock.ANY)
|
||||
self.point.refresh_from_db()
|
||||
self.assertEqual(self.point.status, Point.Status.RELEASING)
|
||||
|
||||
@@ -311,8 +316,9 @@ class PointSettingsTests(BotTestCase):
|
||||
self.point.outbound_proxy = 'socks5://user:secret@proxy.example:1080'
|
||||
self.point.save()
|
||||
card = self.say(texts.SERVER_MENU)
|
||||
self.assertIn('socks5://user:***@proxy.example:1080', card)
|
||||
self.assertIn('socks5://***@proxy.example:1080', card)
|
||||
self.assertNotIn('secret', card)
|
||||
self.assertNotIn('user', card)
|
||||
|
||||
|
||||
class TasksTests(TestCase):
|
||||
@@ -323,13 +329,21 @@ class TasksTests(TestCase):
|
||||
self.point = create_point(owner)
|
||||
self.api = FakeApi()
|
||||
|
||||
def run_task(self, actor, *args, runner=None):
|
||||
with patch_runner(runner or FakeRunner()), mock.patch.object(Bot, 'api', return_value=self.api):
|
||||
actor.fn(*args)
|
||||
def run_task(self, actor, *args, status=Point.Status.INSTALLING, runner=None):
|
||||
"""Запускает задачу, как бот, и выполняет ее сообщение."""
|
||||
self.point.refresh_from_db()
|
||||
with mock.patch.object(actor, 'send') as send:
|
||||
tasks.start(actor, self.point, status, self.chat, *args)
|
||||
with (
|
||||
patch_runner(runner or FakeRunner()),
|
||||
mock.patch.object(Bot, 'api', return_value=self.api),
|
||||
mock.patch.object(tasks.check_point, 'send'),
|
||||
):
|
||||
actor.fn(*send.call_args.args)
|
||||
return self.api.last_text
|
||||
|
||||
def test_install_notifies_success(self):
|
||||
text = self.run_task(tasks.install_point, self.point.pk, self.chat.pk)
|
||||
text = self.run_task(tasks.install_point)
|
||||
self.assertIn(self.point.name, text)
|
||||
self.assertIn('10.0.0.2', text)
|
||||
self.assertIn('готов к работе', text)
|
||||
@@ -337,12 +351,12 @@ class TasksTests(TestCase):
|
||||
|
||||
def test_install_notifies_failure(self):
|
||||
with self.assertLogs('App.tasks', 'ERROR'):
|
||||
text = self.run_task(tasks.install_point, self.point.pk, self.chat.pk, runner=FakeRunner(fail='xui'))
|
||||
text = self.run_task(tasks.install_point, runner=FakeRunner(fail='xui'))
|
||||
self.assertIn('Не удалось', text)
|
||||
self.assertIn('boom', text)
|
||||
|
||||
def test_release_notifies_passwords(self):
|
||||
text = self.run_task(tasks.release_point, self.point.pk, self.chat.pk)
|
||||
text = self.run_task(tasks.release_point, status=Point.Status.RELEASING)
|
||||
self.assertIn('10.0.0.1 - пароль root', text)
|
||||
self.assertFalse(Point.objects.exists())
|
||||
|
||||
@@ -351,10 +365,169 @@ class TasksTests(TestCase):
|
||||
self.point.status = Point.Status.READY
|
||||
self.point.save()
|
||||
proxy = fields.encrypt('socks5://user:secret@proxy.example:1080')
|
||||
text = self.run_task(tasks.set_outbound_proxy, self.point.pk, self.chat.pk, proxy)
|
||||
self.assertIn('user:***@proxy.example', text)
|
||||
text = self.run_task(tasks.set_outbound_proxy, proxy, status=Point.Status.UPDATING)
|
||||
self.assertIn('socks5://***@proxy.example', text)
|
||||
with self.assertLogs('App.tasks', 'ERROR'):
|
||||
text = self.run_task(tasks.set_domain, self.point.pk, self.chat.pk, 'vpn.example.com', runner=FakeRunner(fail='xui_cert'))
|
||||
text = self.run_task(
|
||||
tasks.set_domain, 'vpn.example.com', status=Point.Status.UPDATING, runner=FakeRunner(fail='xui_cert'),
|
||||
)
|
||||
self.assertIn('прежними настройками', text)
|
||||
self.point.refresh_from_db()
|
||||
self.assertEqual((self.point.status, self.point.domain), (Point.Status.READY, ''))
|
||||
|
||||
|
||||
class SupervisorTests(TestCase):
|
||||
"""Задача, которая прервалась, не оставляет точку занятой навсегда."""
|
||||
|
||||
def setUp(self):
|
||||
bot = Bot.objects.create(token='1:test')
|
||||
owner = TelegramUser.objects.create(tg_id=1)
|
||||
self.chat = Chat.objects.create(bot=bot, chat_id=1, type='private', user=owner)
|
||||
self.point = create_point(owner)
|
||||
self.api = FakeApi()
|
||||
patcher = mock.patch.object(Bot, 'api', return_value=self.api)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def start(self, actor=tasks.install_point, status=Point.Status.INSTALLING, *args):
|
||||
with mock.patch.object(actor, 'send') as send, mock.patch.object(tasks.supervise, 'send_with_options') as sup:
|
||||
tasks.start(actor, self.point, status, self.chat, *args)
|
||||
sup.assert_called_once_with(args=(self.point.pk, send.call_args.args[1]), delay=tasks.SUPERVISE_DELAY_MS)
|
||||
return send.call_args.args
|
||||
|
||||
def supervise(self, token):
|
||||
with mock.patch.object(tasks.supervise, 'send_with_options') as again:
|
||||
tasks.supervise.fn(self.point.pk, token)
|
||||
self.point.refresh_from_db()
|
||||
return again.called
|
||||
|
||||
def age_heartbeat(self, delta):
|
||||
Point.objects.filter(pk=self.point.pk).update(task_heartbeat=timezone.now() - delta)
|
||||
|
||||
def test_stuck_task_is_aborted(self):
|
||||
_, token = self.start()
|
||||
Point.objects.claim_task(self.point.pk, token)
|
||||
self.assertTrue(self.supervise(token))
|
||||
self.age_heartbeat(tasks.TASK_STALE + datetime.timedelta(seconds=1))
|
||||
self.assertFalse(self.supervise(token))
|
||||
self.assertEqual(self.point.status, Point.Status.ERROR)
|
||||
self.assertIn('Переустановить', self.point.error)
|
||||
self.assertEqual(self.point.task_token, '')
|
||||
self.assertIn('установка прервалась', self.api.last_text)
|
||||
|
||||
def test_queued_task_waits_longer(self):
|
||||
_, token = self.start()
|
||||
self.age_heartbeat(tasks.TASK_STALE * 2)
|
||||
self.assertTrue(self.supervise(token))
|
||||
self.age_heartbeat(tasks.QUEUE_TIMEOUT + datetime.timedelta(seconds=1))
|
||||
self.assertFalse(self.supervise(token))
|
||||
self.assertEqual(self.point.status, Point.Status.ERROR)
|
||||
# сообщение все же дошло: задача снята, работы нет
|
||||
runner = FakeRunner()
|
||||
with patch_runner(runner):
|
||||
tasks.install_point.fn(self.point.pk, token)
|
||||
self.assertEqual(runner.calls, [])
|
||||
|
||||
def test_interrupted_update_keeps_point_ready(self):
|
||||
token = self.start(tasks.set_domain, Point.Status.UPDATING, 'vpn.example.com')[1]
|
||||
Point.objects.claim_task(self.point.pk, token)
|
||||
self.age_heartbeat(tasks.TASK_STALE * 2)
|
||||
self.supervise(token)
|
||||
self.assertEqual((self.point.status, self.point.domain), (Point.Status.READY, ''))
|
||||
self.assertIn('прервалась', self.point.error)
|
||||
|
||||
def test_supervisor_stops_after_task(self):
|
||||
args = self.start()
|
||||
with patch_runner(FakeRunner()), mock.patch.object(tasks.check_point, 'send'):
|
||||
tasks.install_point.fn(*args)
|
||||
self.assertFalse(self.supervise(args[1]))
|
||||
self.assertEqual((self.point.status, self.point.task_token), (Point.Status.READY, ''))
|
||||
|
||||
def test_redelivered_message_is_not_repeated(self):
|
||||
# воркер упал посреди установки, RabbitMQ доставил сообщение снова
|
||||
args = self.start()
|
||||
Point.objects.claim_task(*args)
|
||||
runner = FakeRunner()
|
||||
with patch_runner(runner):
|
||||
tasks.install_point.fn(*args)
|
||||
self.assertEqual(runner.calls, [])
|
||||
self.point.refresh_from_db()
|
||||
self.assertEqual(self.point.status, Point.Status.ERROR)
|
||||
|
||||
def test_time_limit_aborts_task(self):
|
||||
args = self.start()
|
||||
with (
|
||||
mock.patch.object(services, 'install_point', side_effect=TimeLimitExceeded),
|
||||
self.assertRaises(TimeLimitExceeded),
|
||||
self.assertLogs('App.tasks', 'ERROR'),
|
||||
):
|
||||
tasks.install_point.fn(*args)
|
||||
self.point.refresh_from_db()
|
||||
self.assertEqual((self.point.status, self.point.task_token), (Point.Status.ERROR, ''))
|
||||
|
||||
def test_failed_notification_keeps_result(self):
|
||||
args = self.start()
|
||||
with (
|
||||
patch_runner(FakeRunner()),
|
||||
mock.patch.object(tasks, 'schedule_checks', side_effect=RuntimeError('boom')),
|
||||
self.assertRaises(RuntimeError),
|
||||
self.assertLogs('App.tasks', 'ERROR'),
|
||||
):
|
||||
tasks.install_point.fn(*args)
|
||||
self.point.refresh_from_db()
|
||||
self.assertEqual((self.point.status, self.point.task_token), (Point.Status.READY, ''))
|
||||
|
||||
|
||||
class CheckTests(TestCase):
|
||||
def setUp(self):
|
||||
bot = Bot.objects.create(token='1:test')
|
||||
owner = TelegramUser.objects.create(tg_id=1)
|
||||
Chat.objects.create(bot=bot, chat_id=1, type='private', user=owner)
|
||||
self.point = create_point(owner)
|
||||
self.point.status = Point.Status.READY
|
||||
self.point.save()
|
||||
self.api = FakeApi()
|
||||
patcher = mock.patch.object(Bot, 'api', return_value=self.api)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
with mock.patch.object(tasks.check_point, 'send') as send:
|
||||
tasks.schedule_checks(self.point)
|
||||
self.token = send.call_args.args[1]
|
||||
|
||||
def check(self, problem='', token=None):
|
||||
with (
|
||||
mock.patch.object(services, 'check_point', return_value=problem) as check,
|
||||
mock.patch.object(tasks.check_point, 'send_with_options') as again,
|
||||
):
|
||||
tasks.check_point.fn(self.point.pk, token or self.token)
|
||||
self.point.refresh_from_db()
|
||||
return check.called, again.called
|
||||
|
||||
def test_checks_once_a_day_and_reports_changes(self):
|
||||
self.assertEqual(self.check('панель не отвечает'), (True, True))
|
||||
self.assertEqual(self.point.health_error, 'панель не отвечает')
|
||||
self.assertIn('панель не отвечает', self.api.last_text)
|
||||
self.assertIn('Проверка: панель не отвечает', texts.point_card(self.point))
|
||||
# до следующих суток не проверяет, но цепочка живет
|
||||
self.assertEqual(self.check(), (False, True))
|
||||
Point.objects.filter(pk=self.point.pk).update(checked_at=timezone.now() - tasks.CHECK_INTERVAL)
|
||||
sent = len(self.api.sent)
|
||||
self.check('панель не отвечает')
|
||||
self.assertEqual(len(self.api.sent), sent)
|
||||
Point.objects.filter(pk=self.point.pk).update(checked_at=timezone.now() - tasks.CHECK_INTERVAL)
|
||||
self.check()
|
||||
self.assertIn('снова все в порядке', self.api.last_text)
|
||||
|
||||
def test_busy_point_is_not_checked(self):
|
||||
self.point.set_status(Point.Status.INSTALLING)
|
||||
self.assertEqual(self.check(), (False, True))
|
||||
|
||||
def test_old_chain_stops(self):
|
||||
with mock.patch.object(tasks.check_point, 'send'):
|
||||
tasks.schedule_checks(self.point)
|
||||
self.assertEqual(self.check(token=self.token), (False, False))
|
||||
point_id = self.point.pk
|
||||
self.point.delete()
|
||||
with mock.patch.object(tasks.check_point, 'send_with_options') as again:
|
||||
tasks.check_point.fn(point_id, self.token)
|
||||
again.assert_not_called()
|
||||
Reference in new issue
Block a user