2017-03-22 22:09:38 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.contrib.auth.models import AnonymousUser
|
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from django.test import RequestFactory
|
|
|
|
from functools import wraps
|
|
|
|
from mock import patch
|
|
|
|
if False:
|
|
|
|
from mypy_extensions import NoReturn
|
|
|
|
from typing import Any, Callable, Dict, Mapping, Optional, Text
|
|
|
|
|
|
|
|
from zerver.lib.request import JsonableError
|
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
|
|
|
from zerver.logging_handlers import AdminZulipHandler
|
|
|
|
from zerver.middleware import JsonErrorHandler
|
|
|
|
from zerver.views.compatibility import check_compatibility
|
|
|
|
from zerver.worker.queue_processors import QueueProcessingWorker
|
|
|
|
|
|
|
|
captured_request = None # type: Optional[HttpRequest]
|
|
|
|
captured_exc_info = None
|
|
|
|
def capture_and_throw(domain=None):
|
|
|
|
# type: (Optional[Text]) -> Callable
|
|
|
|
def wrapper(view_func):
|
|
|
|
# type: (Callable[..., HttpResponse]) -> Callable[..., HttpResponse]
|
|
|
|
@wraps(view_func)
|
|
|
|
def wrapped_view(request, *args, **kwargs):
|
|
|
|
# type: (HttpRequest, *Any, **Any) -> NoReturn
|
|
|
|
global captured_request
|
|
|
|
captured_request = request
|
|
|
|
try:
|
|
|
|
raise Exception("Request error")
|
|
|
|
except Exception as e:
|
|
|
|
global captured_exc_info
|
|
|
|
captured_exc_info = sys.exc_info()
|
|
|
|
raise e
|
|
|
|
return wrapped_view
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
class AdminZulipHandlerTest(ZulipTestCase):
|
|
|
|
logger = logging.getLogger('django')
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.handler = AdminZulipHandler()
|
|
|
|
# Prevent the exceptions we're going to raise from being printed
|
|
|
|
# You may want to disable this when debugging tests
|
|
|
|
settings.LOGGING_NOT_DISABLED = False
|
|
|
|
|
2017-04-26 03:52:12 +02:00
|
|
|
global captured_exc_info
|
|
|
|
global captured_request
|
|
|
|
captured_request = None
|
|
|
|
captured_exc_info = None
|
|
|
|
|
2017-03-22 22:09:38 +01:00
|
|
|
def tearDown(self):
|
|
|
|
# type: () -> None
|
|
|
|
settings.LOGGING_NOT_DISABLED = True
|
|
|
|
|
2017-03-26 06:54:56 +02:00
|
|
|
def get_admin_zulip_handler(self, logger):
|
|
|
|
# type: (logging.Logger) -> Any
|
|
|
|
|
|
|
|
# Ensure that AdminEmailHandler does not get filtered out
|
|
|
|
# even with DEBUG=True.
|
|
|
|
admin_email_handler = [
|
|
|
|
h for h in logger.handlers
|
|
|
|
if h.__class__.__name__ == "AdminZulipHandler"
|
|
|
|
][0]
|
|
|
|
return admin_email_handler
|
|
|
|
|
|
|
|
def test_basic(self):
|
|
|
|
# type: () -> None
|
|
|
|
"""A random exception passes happily through AdminZulipHandler"""
|
|
|
|
handler = self.get_admin_zulip_handler(self.logger)
|
|
|
|
try:
|
|
|
|
raise Exception("Testing Error!")
|
|
|
|
except Exception:
|
|
|
|
exc_info = sys.exc_info()
|
|
|
|
record = self.logger.makeRecord('name', logging.ERROR, 'function', 16, 'message', None, exc_info) # type: ignore # https://github.com/python/typeshed/pull/1100
|
|
|
|
handler.emit(record)
|
|
|
|
|
2017-03-22 22:09:38 +01:00
|
|
|
def run_handler(self, record):
|
|
|
|
# type: (logging.LogRecord) -> Dict[str, Any]
|
|
|
|
with patch('zerver.logging_handlers.queue_json_publish') as patched_publish:
|
|
|
|
self.handler.emit(record)
|
|
|
|
patched_publish.assert_called_once()
|
|
|
|
event = patched_publish.call_args[0][1]
|
|
|
|
self.assertIn("report", event)
|
|
|
|
return event["report"]
|
|
|
|
|
2017-04-26 03:52:12 +02:00
|
|
|
def test_long_exception_request(self):
|
|
|
|
# type: () -> None
|
|
|
|
"""A request with with no stack where report.getMessage() has newlines
|
|
|
|
in it is handled properly"""
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2017-04-26 03:52:12 +02:00
|
|
|
with patch("zerver.decorator.rate_limit") as rate_limit_patch:
|
|
|
|
rate_limit_patch.side_effect = capture_and_throw
|
|
|
|
result = self.client_get("/json/users")
|
|
|
|
self.assert_json_error(result, "Internal server error", status_code=500)
|
|
|
|
rate_limit_patch.assert_called_once()
|
|
|
|
|
|
|
|
global captured_request
|
|
|
|
global captured_exc_info
|
2017-04-26 08:44:41 +02:00
|
|
|
record = self.logger.makeRecord('name', logging.ERROR, 'function', 15, # type: ignore # https://github.com/python/typeshed/pull/1100
|
2017-04-26 03:52:12 +02:00
|
|
|
'message\nmoremesssage\nmore', None,
|
2017-04-26 08:44:41 +02:00
|
|
|
None)
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request = captured_request # type: ignore # this field is dynamically added
|
2017-04-26 03:52:12 +02:00
|
|
|
|
|
|
|
report = self.run_handler(record)
|
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|
|
|
|
self.assertEqual(report['stack_trace'], 'message\nmoremesssage\nmore')
|
|
|
|
self.assertEqual(report['message'], 'message')
|
|
|
|
|
2017-03-22 22:09:38 +01:00
|
|
|
def test_request(self):
|
|
|
|
# type: () -> None
|
|
|
|
"""A normal request is handled properly"""
|
2017-05-24 05:08:49 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
self.login(email)
|
2017-03-22 22:09:38 +01:00
|
|
|
with patch("zerver.decorator.rate_limit") as rate_limit_patch:
|
|
|
|
rate_limit_patch.side_effect = capture_and_throw
|
|
|
|
result = self.client_get("/json/users")
|
|
|
|
self.assert_json_error(result, "Internal server error", status_code=500)
|
|
|
|
rate_limit_patch.assert_called_once()
|
|
|
|
|
|
|
|
global captured_request
|
|
|
|
global captured_exc_info
|
|
|
|
record = self.logger.makeRecord('name', logging.ERROR, 'function', 15, 'message', None, captured_exc_info) # type: ignore # https://github.com/python/typeshed/pull/1100
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request = captured_request # type: ignore # this field is dynamically added
|
2017-03-22 22:09:38 +01:00
|
|
|
|
|
|
|
report = self.run_handler(record)
|
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
|
|
|
|
# Test that `add_request_metadata` throwing an exception is fine
|
|
|
|
with patch("zerver.logging_handlers.traceback.print_exc"):
|
|
|
|
with patch("zerver.logging_handlers.add_request_metadata",
|
|
|
|
side_effect=Exception("Unexpected exception!")):
|
|
|
|
report = self.run_handler(record)
|
|
|
|
self.assertNotIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertEqual(report["stack_trace"], "See /var/log/zulip/errors.log")
|
|
|
|
|
|
|
|
# Check anonymous user is handled correctly
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request.user = AnonymousUser() # type: ignore # this field is dynamically added
|
2017-03-26 07:00:08 +02:00
|
|
|
report = self.run_handler(record)
|
2017-04-25 22:46:11 +02:00
|
|
|
self.assertIn("host", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|
|
|
|
|
|
|
|
# Now simulate a DisallowedHost exception
|
|
|
|
def get_host_error():
|
|
|
|
# type: () -> None
|
|
|
|
raise Exception("Get Host Failure!")
|
2017-06-04 11:37:26 +02:00
|
|
|
orig_get_host = record.request.get_host # type: ignore # this field is dynamically added
|
|
|
|
record.request.get_host = get_host_error # type: ignore # this field is dynamically added
|
2017-03-26 07:00:08 +02:00
|
|
|
report = self.run_handler(record)
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request.get_host = orig_get_host # type: ignore # this field is dynamically added
|
2017-04-25 22:46:11 +02:00
|
|
|
self.assertIn("host", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|
|
|
|
|
|
|
|
# Test an exception_filter exception
|
|
|
|
with patch("zerver.logging_handlers.get_exception_reporter_filter",
|
|
|
|
return_value=15):
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request.method = "POST" # type: ignore # this field is dynamically added
|
2017-03-26 07:00:08 +02:00
|
|
|
report = self.run_handler(record)
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request.method = "GET" # type: ignore # this field is dynamically added
|
2017-04-25 22:46:11 +02:00
|
|
|
self.assertIn("host", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|
|
|
|
|
|
|
|
# Test the catch-all exception handler doesn't throw
|
|
|
|
with patch('zerver.logging_handlers.queue_json_publish',
|
|
|
|
side_effect=Exception("queue error")):
|
|
|
|
self.handler.emit(record)
|
|
|
|
|
|
|
|
# Test the STAGING_ERROR_NOTIFICATIONS code path
|
|
|
|
with self.settings(STAGING_ERROR_NOTIFICATIONS=True):
|
|
|
|
with patch('zerver.lib.error_notify.notify_server_error',
|
|
|
|
side_effect=Exception("queue error")):
|
|
|
|
self.handler.emit(record)
|
|
|
|
|
|
|
|
# Test no exc_info
|
|
|
|
record.exc_info = None
|
|
|
|
report = self.run_handler(record)
|
2017-04-25 22:46:11 +02:00
|
|
|
self.assertIn("host", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertEqual(report["stack_trace"], None)
|
|
|
|
|
|
|
|
# Test arbitrary exceptions from request.user
|
2017-06-04 11:37:26 +02:00
|
|
|
record.request.user = None # type: ignore # this field is dynamically added
|
2017-03-26 07:00:08 +02:00
|
|
|
with patch("zerver.logging_handlers.traceback.print_exc"):
|
|
|
|
report = self.run_handler(record)
|
2017-04-25 22:46:11 +02:00
|
|
|
self.assertIn("host", report)
|
2017-03-26 07:00:08 +02:00
|
|
|
self.assertIn("user_email", report)
|
|
|
|
self.assertIn("message", report)
|
|
|
|
self.assertIn("stack_trace", report)
|