2017-03-08 12:18:27 +01:00
|
|
|
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
import ujson
|
2017-09-15 09:38:12 +02:00
|
|
|
import smtplib
|
2017-03-08 12:18:27 +01:00
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.http import HttpResponse
|
|
|
|
from django.test import TestCase
|
2017-10-06 07:15:58 +02:00
|
|
|
from mock import patch, MagicMock
|
2017-03-08 12:18:27 +01:00
|
|
|
from typing import Any, Callable, Dict, List, Mapping, Tuple
|
|
|
|
|
|
|
|
from zerver.lib.test_helpers import simulated_queue_client
|
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2017-05-08 17:54:11 +02:00
|
|
|
from zerver.models import get_client, UserActivity
|
2017-03-08 12:18:27 +01:00
|
|
|
from zerver.worker import queue_processors
|
2017-11-10 15:00:45 +01:00
|
|
|
from zerver.worker.queue_processors import (
|
|
|
|
get_active_worker_queues,
|
|
|
|
QueueProcessingWorker,
|
|
|
|
LoopQueueProcessingWorker,
|
|
|
|
)
|
2017-03-08 12:18:27 +01:00
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
class WorkerTest(ZulipTestCase):
|
2017-11-05 11:49:43 +01:00
|
|
|
class FakeClient:
|
2017-11-05 10:51:25 +01:00
|
|
|
def __init__(self) -> None:
|
2017-11-02 17:34:39 +01:00
|
|
|
self.consumers = {} # type: Dict[str, Callable[[Dict[str, Any]], None]]
|
2017-05-07 20:02:56 +02:00
|
|
|
self.queue = [] # type: List[Tuple[str, Dict[str, Any]]]
|
2017-03-08 12:18:27 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def register_json_consumer(self,
|
|
|
|
queue_name: str,
|
|
|
|
callback: Callable[[Dict[str, Any]], None]) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
self.consumers[queue_name] = callback
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def start_consuming(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
for queue_name, data in self.queue:
|
|
|
|
callback = self.consumers[queue_name]
|
|
|
|
callback(data)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_mirror_worker(self) -> None:
|
2017-04-05 11:46:14 +02:00
|
|
|
fake_client = self.FakeClient()
|
|
|
|
data = [
|
|
|
|
dict(
|
|
|
|
message=u'\xf3test',
|
|
|
|
time=time.time(),
|
2017-05-24 05:08:49 +02:00
|
|
|
rcpt_to=self.example_email('hamlet'),
|
2017-04-05 11:46:14 +02:00
|
|
|
),
|
|
|
|
dict(
|
|
|
|
message='\xf3test',
|
|
|
|
time=time.time(),
|
2017-05-24 05:08:49 +02:00
|
|
|
rcpt_to=self.example_email('hamlet'),
|
2017-04-05 11:46:14 +02:00
|
|
|
),
|
|
|
|
dict(
|
|
|
|
message='test',
|
|
|
|
time=time.time(),
|
2017-05-24 05:08:49 +02:00
|
|
|
rcpt_to=self.example_email('hamlet'),
|
2017-04-05 11:46:14 +02:00
|
|
|
),
|
|
|
|
]
|
|
|
|
for element in data:
|
|
|
|
fake_client.queue.append(('email_mirror', element))
|
|
|
|
|
|
|
|
with patch('zerver.worker.queue_processors.mirror_email'):
|
|
|
|
with simulated_queue_client(lambda: fake_client):
|
|
|
|
worker = queue_processors.MirrorWorker()
|
|
|
|
worker.setup()
|
|
|
|
worker.start()
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_email_sending_worker_retries(self) -> None:
|
2017-09-15 09:38:12 +02:00
|
|
|
"""Tests the retry_send_email_failures decorator to make sure it
|
|
|
|
retries sending the email 3 times and then gives up."""
|
|
|
|
fake_client = self.FakeClient()
|
|
|
|
|
2017-10-28 03:14:13 +02:00
|
|
|
data = {'test': 'test', 'id': 'test_missed'}
|
2017-09-15 09:38:12 +02:00
|
|
|
fake_client.queue.append(('missedmessage_email_senders', data))
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def fake_publish(queue_name: str,
|
|
|
|
event: Dict[str, Any],
|
|
|
|
processor: Callable[[Any], None]) -> None:
|
2017-09-15 09:38:12 +02:00
|
|
|
fake_client.queue.append((queue_name, event))
|
|
|
|
|
|
|
|
with simulated_queue_client(lambda: fake_client):
|
|
|
|
worker = queue_processors.MissedMessageSendingWorker()
|
|
|
|
worker.setup()
|
|
|
|
with patch('zerver.worker.queue_processors.send_email_from_dict',
|
|
|
|
side_effect=smtplib.SMTPServerDisconnected), \
|
|
|
|
patch('zerver.lib.queue.queue_json_publish',
|
|
|
|
side_effect=fake_publish), \
|
|
|
|
patch('logging.exception'):
|
|
|
|
worker.start()
|
|
|
|
|
|
|
|
self.assertEqual(data['failed_tries'], 4)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_signups_worker_retries(self) -> None:
|
2017-10-06 07:15:58 +02:00
|
|
|
"""Tests the retry logic of signups queue."""
|
|
|
|
fake_client = self.FakeClient()
|
|
|
|
|
|
|
|
user_id = self.example_user('hamlet').id
|
2017-10-28 03:14:13 +02:00
|
|
|
data = {'user_id': user_id, 'id': 'test_missed'}
|
2017-10-06 07:15:58 +02:00
|
|
|
fake_client.queue.append(('signups', data))
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def fake_publish(queue_name: str, event: Dict[str, Any], processor: Callable[[Any], None]) -> None:
|
2017-10-06 07:15:58 +02:00
|
|
|
fake_client.queue.append((queue_name, event))
|
|
|
|
|
|
|
|
fake_response = MagicMock()
|
|
|
|
fake_response.status_code = 400
|
|
|
|
fake_response.text = ujson.dumps({'title': ''})
|
|
|
|
with simulated_queue_client(lambda: fake_client):
|
|
|
|
worker = queue_processors.SignupWorker()
|
|
|
|
worker.setup()
|
|
|
|
with patch('zerver.worker.queue_processors.requests.post',
|
|
|
|
return_value=fake_response), \
|
|
|
|
patch('zerver.lib.queue.queue_json_publish',
|
|
|
|
side_effect=fake_publish), \
|
|
|
|
patch('logging.info'), \
|
|
|
|
self.settings(MAILCHIMP_API_KEY='one-two',
|
|
|
|
PRODUCTION=True,
|
|
|
|
ZULIP_FRIENDS_LIST_ID='id'):
|
|
|
|
worker.start()
|
|
|
|
|
|
|
|
self.assertEqual(data['failed_tries'], 4)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_UserActivityWorker(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
fake_client = self.FakeClient()
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user = self.example_user('hamlet')
|
2017-03-08 12:18:27 +01:00
|
|
|
UserActivity.objects.filter(
|
|
|
|
user_profile = user.id,
|
|
|
|
client = get_client('ios')
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
data = dict(
|
|
|
|
user_profile_id = user.id,
|
|
|
|
client = 'ios',
|
|
|
|
time = time.time(),
|
|
|
|
query = 'send_message'
|
|
|
|
)
|
|
|
|
fake_client.queue.append(('user_activity', data))
|
|
|
|
|
|
|
|
with simulated_queue_client(lambda: fake_client):
|
|
|
|
worker = queue_processors.UserActivityWorker()
|
|
|
|
worker.setup()
|
|
|
|
worker.start()
|
|
|
|
activity_records = UserActivity.objects.filter(
|
|
|
|
user_profile = user.id,
|
|
|
|
client = get_client('ios')
|
|
|
|
)
|
|
|
|
self.assertTrue(len(activity_records), 1)
|
|
|
|
self.assertTrue(activity_records[0].count, 1)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_error_handling(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
processed = []
|
|
|
|
|
|
|
|
@queue_processors.assign_queue('unreliable_worker')
|
|
|
|
class UnreliableWorker(queue_processors.QueueProcessingWorker):
|
2017-11-05 10:51:25 +01:00
|
|
|
def consume(self, data: Mapping[str, Any]) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
if data["type"] == 'unexpected behaviour':
|
|
|
|
raise Exception('Worker task not performing as expected!')
|
|
|
|
processed.append(data["type"])
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def _log_problem(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
|
|
|
|
# keep the tests quiet
|
|
|
|
pass
|
|
|
|
|
|
|
|
fake_client = self.FakeClient()
|
|
|
|
for msg in ['good', 'fine', 'unexpected behaviour', 'back to normal']:
|
|
|
|
fake_client.queue.append(('unreliable_worker', {'type': msg}))
|
|
|
|
|
|
|
|
fn = os.path.join(settings.QUEUE_ERROR_DIR, 'unreliable_worker.errors')
|
|
|
|
try:
|
|
|
|
os.remove(fn)
|
|
|
|
except OSError: # nocoverage # error handling for the directory not existing
|
|
|
|
pass
|
|
|
|
|
|
|
|
with simulated_queue_client(lambda: fake_client):
|
|
|
|
worker = UnreliableWorker()
|
|
|
|
worker.setup()
|
|
|
|
worker.start()
|
|
|
|
|
|
|
|
self.assertEqual(processed, ['good', 'fine', 'back to normal'])
|
|
|
|
line = open(fn).readline().strip()
|
|
|
|
event = ujson.loads(line.split('\t')[1])
|
|
|
|
self.assertEqual(event["type"], 'unexpected behaviour')
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_worker_noname(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
class TestWorker(queue_processors.QueueProcessingWorker):
|
2017-11-05 10:51:25 +01:00
|
|
|
def __init__(self) -> None:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().__init__()
|
2017-03-08 12:18:27 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def consume(self, data: Mapping[str, Any]) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
pass # nocoverage # this is intentionally not called
|
|
|
|
with self.assertRaises(queue_processors.WorkerDeclarationException):
|
|
|
|
TestWorker()
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_worker_noconsume(self) -> None:
|
2017-03-08 12:18:27 +01:00
|
|
|
@queue_processors.assign_queue('test_worker')
|
|
|
|
class TestWorker(queue_processors.QueueProcessingWorker):
|
2017-11-05 10:51:25 +01:00
|
|
|
def __init__(self) -> None:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().__init__()
|
2017-03-08 12:18:27 +01:00
|
|
|
|
|
|
|
with self.assertRaises(queue_processors.WorkerDeclarationException):
|
|
|
|
worker = TestWorker()
|
|
|
|
worker.consume({})
|
2017-11-10 15:00:45 +01:00
|
|
|
|
|
|
|
def test_get_active_worker_queues(self) -> None:
|
|
|
|
worker_queue_count = (len(QueueProcessingWorker.__subclasses__()) +
|
|
|
|
len(LoopQueueProcessingWorker.__subclasses__()) - 1)
|
|
|
|
self.assertEqual(worker_queue_count, len(get_active_worker_queues()))
|
|
|
|
self.assertEqual(1, len(get_active_worker_queues(queue_type='test')))
|