2020-10-09 03:32:00 +02:00
|
|
|
import subprocess
|
2021-02-15 23:56:29 +01:00
|
|
|
from typing import Callable, ContextManager, Dict, List, Tuple
|
2020-06-11 00:54:34 +02:00
|
|
|
from unittest import mock
|
2016-09-16 15:25:04 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2017-01-24 07:54:18 +01:00
|
|
|
from django.test import override_settings
|
2020-06-11 00:54:34 +02:00
|
|
|
|
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2020-08-14 10:03:36 +02:00
|
|
|
from zerver.lib.test_helpers import mock_queue_publish
|
2016-09-16 15:25:04 +02:00
|
|
|
from zerver.lib.utils import statsd
|
|
|
|
|
|
|
|
|
2021-02-15 23:56:29 +01:00
|
|
|
def fix_params(raw_params: Dict[str, object]) -> Dict[str, str]:
|
2016-09-16 15:25:04 +02:00
|
|
|
# A few of our few legacy endpoints need their
|
|
|
|
# individual parameters serialized as JSON.
|
2020-08-07 01:09:47 +02:00
|
|
|
return {k: orjson.dumps(v).decode() for k, v in raw_params.items()}
|
2016-09-16 15:25:04 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-05 11:49:43 +01:00
|
|
|
class StatsMock:
|
2021-02-15 23:56:29 +01:00
|
|
|
def __init__(self, settings: Callable[..., ContextManager[None]]) -> None:
|
2016-09-16 15:25:04 +02:00
|
|
|
self.settings = settings
|
|
|
|
self.real_impl = statsd
|
2021-02-15 23:56:29 +01:00
|
|
|
self.func_calls: List[Tuple[str, Tuple[object, ...]]] = []
|
2016-09-16 15:25:04 +02:00
|
|
|
|
2021-02-15 23:56:29 +01:00
|
|
|
def __getattr__(self, name: str) -> Callable[..., None]:
|
|
|
|
def f(*args: object) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
with self.settings(STATSD_HOST=""):
|
2016-09-16 15:25:04 +02:00
|
|
|
getattr(self.real_impl, name)(*args)
|
|
|
|
self.func_calls.append((name, args))
|
|
|
|
|
|
|
|
return f
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2016-09-16 15:25:04 +02:00
|
|
|
class TestReport(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_send_time(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("hamlet")
|
2016-09-16 15:25:04 +02:00
|
|
|
|
|
|
|
params = dict(
|
|
|
|
time=5,
|
|
|
|
received=6,
|
|
|
|
displayed=7,
|
2021-02-12 08:20:45 +01:00
|
|
|
locally_echoed="true",
|
|
|
|
rendered_content_disparity="true",
|
2016-09-16 15:25:04 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
stats_mock = StatsMock(self.settings)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock.patch("zerver.views.report.statsd", wraps=stats_mock):
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/send_times", params)
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
expected_calls = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("timing", ("endtoend.send_time.zulip", 5)),
|
|
|
|
("timing", ("endtoend.receive_time.zulip", 6)),
|
|
|
|
("timing", ("endtoend.displayed_time.zulip", 7)),
|
|
|
|
("incr", ("locally_echoed",)),
|
|
|
|
("incr", ("render_disparity",)),
|
2016-09-16 15:25:04 +02:00
|
|
|
]
|
|
|
|
self.assertEqual(stats_mock.func_calls, expected_calls)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_narrow_time(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("hamlet")
|
2016-09-16 15:25:04 +02:00
|
|
|
|
|
|
|
params = dict(
|
|
|
|
initial_core=5,
|
|
|
|
initial_free=6,
|
|
|
|
network=7,
|
|
|
|
)
|
|
|
|
|
|
|
|
stats_mock = StatsMock(self.settings)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock.patch("zerver.views.report.statsd", wraps=stats_mock):
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/narrow_times", params)
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
expected_calls = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("timing", ("narrow.initial_core.zulip", 5)),
|
|
|
|
("timing", ("narrow.initial_free.zulip", 6)),
|
|
|
|
("timing", ("narrow.network.zulip", 7)),
|
2016-09-16 15:25:04 +02:00
|
|
|
]
|
|
|
|
self.assertEqual(stats_mock.func_calls, expected_calls)
|
|
|
|
|
2020-08-31 09:22:40 +02:00
|
|
|
def test_anonymous_user_narrow_time(self) -> None:
|
|
|
|
params = dict(
|
|
|
|
initial_core=5,
|
|
|
|
initial_free=6,
|
|
|
|
network=7,
|
|
|
|
)
|
|
|
|
|
|
|
|
stats_mock = StatsMock(self.settings)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock.patch("zerver.views.report.statsd", wraps=stats_mock):
|
2020-08-31 09:22:40 +02:00
|
|
|
result = self.client_post("/json/report/narrow_times", params)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
expected_calls = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("timing", ("narrow.initial_core.zulip", 5)),
|
|
|
|
("timing", ("narrow.initial_free.zulip", 6)),
|
|
|
|
("timing", ("narrow.network.zulip", 7)),
|
2020-08-31 09:22:40 +02:00
|
|
|
]
|
|
|
|
self.assertEqual(stats_mock.func_calls, expected_calls)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_unnarrow_time(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("hamlet")
|
2016-09-16 15:25:04 +02:00
|
|
|
|
|
|
|
params = dict(
|
2020-08-31 09:22:40 +02:00
|
|
|
initial_core=5,
|
|
|
|
initial_free=6,
|
|
|
|
)
|
|
|
|
|
|
|
|
stats_mock = StatsMock(self.settings)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock.patch("zerver.views.report.statsd", wraps=stats_mock):
|
2020-08-31 09:22:40 +02:00
|
|
|
result = self.client_post("/json/report/unnarrow_times", params)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
expected_calls = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("timing", ("unnarrow.initial_core.zulip", 5)),
|
|
|
|
("timing", ("unnarrow.initial_free.zulip", 6)),
|
2020-08-31 09:22:40 +02:00
|
|
|
]
|
|
|
|
self.assertEqual(stats_mock.func_calls, expected_calls)
|
|
|
|
|
|
|
|
def test_anonymous_user_unnarrow_time(self) -> None:
|
|
|
|
params = dict(
|
2016-09-16 15:25:04 +02:00
|
|
|
initial_core=5,
|
|
|
|
initial_free=6,
|
|
|
|
)
|
|
|
|
|
|
|
|
stats_mock = StatsMock(self.settings)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock.patch("zerver.views.report.statsd", wraps=stats_mock):
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/unnarrow_times", params)
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
expected_calls = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("timing", ("unnarrow.initial_core.zulip", 5)),
|
|
|
|
("timing", ("unnarrow.initial_free.zulip", 6)),
|
2016-09-16 15:25:04 +02:00
|
|
|
]
|
|
|
|
self.assertEqual(stats_mock.func_calls, expected_calls)
|
|
|
|
|
2017-01-24 07:54:18 +01:00
|
|
|
@override_settings(BROWSER_ERROR_REPORTING=True)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_report_error(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
user = self.example_user("hamlet")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(user)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.make_stream("errors", user.realm)
|
2016-09-16 15:25:04 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
params = fix_params(
|
|
|
|
dict(
|
2021-02-12 08:20:45 +01:00
|
|
|
message="hello",
|
|
|
|
stacktrace="trace",
|
2021-02-12 08:19:30 +01:00
|
|
|
ui_message=True,
|
2021-02-12 08:20:45 +01:00
|
|
|
user_agent="agent",
|
|
|
|
href="href",
|
|
|
|
log="log",
|
|
|
|
more_info=dict(foo="bar", draft_content="**draft**"),
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
)
|
2016-09-16 15:25:04 +02:00
|
|
|
|
|
|
|
subprocess_mock = mock.patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver.views.report.subprocess.check_output",
|
2020-10-09 03:32:00 +02:00
|
|
|
side_effect=subprocess.CalledProcessError(1, []),
|
2016-09-16 15:25:04 +02:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
with mock_queue_publish("zerver.views.report.queue_json_publish") as m, subprocess_mock:
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/error", params)
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
report = m.call_args[0][1]["report"]
|
|
|
|
for k in set(params) - {"ui_message", "more_info"}:
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assertEqual(report[k], params[k])
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(report["more_info"], dict(foo="bar", draft_content="'**xxxxx**'"))
|
|
|
|
self.assertEqual(report["user_email"], user.delivery_email)
|
2016-09-16 15:25:04 +02:00
|
|
|
|
2017-10-13 02:22:47 +02:00
|
|
|
# Teset with no more_info
|
2021-02-12 08:20:45 +01:00
|
|
|
del params["more_info"]
|
|
|
|
with mock_queue_publish("zerver.views.report.queue_json_publish") as m, subprocess_mock:
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/error", params)
|
2017-10-13 02:22:47 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-01-24 07:54:18 +01:00
|
|
|
with self.settings(BROWSER_ERROR_REPORTING=False):
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/error", params)
|
2016-09-16 15:25:04 +02:00
|
|
|
self.assert_json_success(result)
|
2017-03-01 04:19:56 +01:00
|
|
|
|
|
|
|
# If js_source_map is present, then the stack trace should be annotated.
|
2017-03-01 06:42:31 +01:00
|
|
|
# DEVELOPMENT=False and TEST_SUITE=False are necessary to ensure that
|
2017-03-01 04:19:56 +01:00
|
|
|
# js_source_map actually gets instantiated.
|
2021-02-12 08:19:30 +01:00
|
|
|
with self.settings(DEVELOPMENT=False, TEST_SUITE=False), mock.patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver.lib.unminify.SourceMap.annotate_stacktrace"
|
|
|
|
) as annotate, self.assertLogs(level="INFO") as info_logs:
|
2017-10-16 22:07:19 +02:00
|
|
|
result = self.client_post("/json/report/error", params)
|
2017-03-01 04:19:56 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
# fix_params (see above) adds quotes when JSON encoding.
|
|
|
|
annotate.assert_called_once_with('"trace"')
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
2021-02-12 08:20:45 +01:00
|
|
|
info_logs.output, ["INFO:root:Processing traceback with type browser for None"]
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-04-11 05:50:08 +02:00
|
|
|
|
2018-12-17 00:10:20 +01:00
|
|
|
# Now test without authentication.
|
|
|
|
self.logout()
|
2021-02-12 08:19:30 +01:00
|
|
|
with self.settings(DEVELOPMENT=False, TEST_SUITE=False), mock.patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver.lib.unminify.SourceMap.annotate_stacktrace"
|
|
|
|
) as annotate, self.assertLogs(level="INFO") as info_logs:
|
2018-12-17 00:10:20 +01:00
|
|
|
result = self.client_post("/json/report/error", params)
|
|
|
|
self.assert_json_success(result)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
2021-02-12 08:20:45 +01:00
|
|
|
info_logs.output, ["INFO:root:Processing traceback with type browser for None"]
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-12-17 00:10:20 +01:00
|
|
|
|
2018-04-11 05:50:08 +02:00
|
|
|
def test_report_csp_violations(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
fixture_data = self.fixture_data("csp_report.json")
|
|
|
|
with self.assertLogs(level="WARNING") as warn_logs:
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_post(
|
|
|
|
"/report/csp_violations", fixture_data, content_type="application/json"
|
|
|
|
)
|
2018-04-11 05:50:08 +02:00
|
|
|
self.assert_json_success(result)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
|
|
|
warn_logs.output,
|
|
|
|
[
|
|
|
|
"WARNING:root:CSP Violation in Document(''). Blocked URI(''), Original Policy(''), Violated Directive(''), Effective Directive(''), Disposition(''), Referrer(''), Status Code(''), Script Sample('')"
|
|
|
|
],
|
|
|
|
)
|