test_process_issue_event.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import uuid
  2. from django.urls import reverse
  3. from apps.issue_events.constants import EventStatus
  4. from apps.issue_events.models import Issue, IssueEvent, IssueHash
  5. from ..process_event import process_issue_events
  6. from ..schema import (
  7. ErrorIssueEventSchema,
  8. InterchangeIssueEvent,
  9. IssueEventSchema,
  10. )
  11. from .utils import EventIngestTestCase
  12. COMPAT_TEST_DATA_DIR = "events/test_data"
  13. class IssueEventIngestTestCase(EventIngestTestCase):
  14. """
  15. These tests bypass the API and celery. They test the event ingest logic itself.
  16. This file should be large are test the following use cases
  17. - Multiple event saved at the same time
  18. - Sentry API compatibility
  19. - Default, Error, and CSP types
  20. - Graceful failure such as duplicate event ids or invalid data
  21. """
  22. def test_two_events(self):
  23. events = []
  24. for _ in range(2):
  25. payload = IssueEventSchema()
  26. events.append(
  27. InterchangeIssueEvent(project_id=self.project.id, payload=payload)
  28. )
  29. with self.assertNumQueries(12):
  30. process_issue_events(events)
  31. self.assertEqual(Issue.objects.count(), 1)
  32. self.assertEqual(IssueHash.objects.count(), 1)
  33. self.assertEqual(IssueEvent.objects.count(), 2)
  34. def test_reopen_resolved_issue(self):
  35. event = InterchangeIssueEvent(
  36. project_id=self.project.id, payload=IssueEventSchema()
  37. )
  38. process_issue_events([event])
  39. issue = Issue.objects.first()
  40. issue.status = EventStatus.RESOLVED
  41. issue.save()
  42. event.event_id = uuid.uuid4().hex
  43. process_issue_events([event])
  44. issue.refresh_from_db()
  45. self.assertEqual(issue.status, EventStatus.UNRESOLVED)
  46. class SentryCompatTestCase(IssueEventIngestTestCase):
  47. """
  48. These tests specifically test former open source sentry compatibility
  49. But otherwise are part of issue event ingest testing
  50. """
  51. def setUp(self):
  52. super().setUp()
  53. self.create_logged_in_user()
  54. def get_json_test_data(self, name: str):
  55. """Get incoming event, sentry json, sentry api event"""
  56. event = self.get_json_data(
  57. f"{COMPAT_TEST_DATA_DIR}/incoming_events/{name}.json"
  58. )
  59. sentry_json = self.get_json_data(
  60. f"{COMPAT_TEST_DATA_DIR}/oss_sentry_json/{name}.json"
  61. )
  62. # Force captured test data to match test generated data
  63. sentry_json["project"] = self.project.id
  64. api_sentry_event = self.get_json_data(
  65. f"{COMPAT_TEST_DATA_DIR}/oss_sentry_events/{name}.json"
  66. )
  67. return event, sentry_json, api_sentry_event
  68. def get_event_json(self, event: IssueEvent):
  69. return self.client.get(
  70. reverse(
  71. "api:get_event_json",
  72. kwargs={
  73. "organization_slug": self.organization.slug,
  74. "issue_id": event.issue_id,
  75. "event_id": event.id,
  76. },
  77. )
  78. ).json()
  79. # Upgrade functions handle intentional differences between GlitchTip and Sentry OSS
  80. def upgrade_title(self, value: str):
  81. """Sentry OSS uses ... while GlitchTip uses unicode …"""
  82. if value[-1] == "…":
  83. return value[:-4]
  84. return value.strip("...")
  85. def upgrade_metadata(self, value: dict):
  86. value["title"] = self.upgrade_title(value["title"])
  87. return value
  88. def assertCompareData(self, data1: dict, data2: dict, fields: list[str]):
  89. """Compare data of two dict objects. Compare only provided fields list"""
  90. for field in fields:
  91. field_value1 = data1.get(field)
  92. field_value2 = data2.get(field)
  93. if field == "datetime":
  94. # Check that it's close enough
  95. field_value1 = field_value1[:23]
  96. field_value2 = field_value2[:23]
  97. if field == "title" and isinstance(field_value1, str):
  98. field_value1 = self.upgrade_title(field_value1)
  99. if field_value2:
  100. field_value2 = self.upgrade_title(field_value2)
  101. if (
  102. field == "metadata"
  103. and isinstance(field_value1, dict)
  104. and field_value1.get("title")
  105. ):
  106. field_value1 = self.upgrade_metadata(field_value1)
  107. if field_value2:
  108. field_value2 = self.upgrade_metadata(field_value2)
  109. self.assertEqual(
  110. field_value1,
  111. field_value2,
  112. f"Failed for field '{field}'",
  113. )
  114. def get_project_events_detail(self, event_id: str):
  115. return reverse(
  116. "api:get_project_issue_event",
  117. kwargs={
  118. "organization_slug": self.project.organization.slug,
  119. "project_slug": self.project.slug,
  120. "event_id": event_id,
  121. },
  122. )
  123. def submit_event(self, event_data: dict) -> IssueEvent:
  124. event = InterchangeIssueEvent(
  125. event_id=event_data["event_id"],
  126. project_id=self.project.id,
  127. payload=ErrorIssueEventSchema(**event_data),
  128. )
  129. process_issue_events([event])
  130. return IssueEvent.objects.get(pk=event.event_id)
  131. def test_template_error(self):
  132. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  133. "django_template_error"
  134. )
  135. event = self.submit_event(sdk_error)
  136. url = self.get_project_events_detail(event.id.hex)
  137. res = self.client.get(url)
  138. res_data = res.json()
  139. self.assertEqual(res.status_code, 200)
  140. self.assertCompareData(res_data, sentry_data, ["culprit", "title", "metadata"])
  141. res_frames = res_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]
  142. frames = sentry_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]
  143. for i in range(6):
  144. # absPath don't always match - needs fixed
  145. self.assertCompareData(res_frames[i], frames[i], ["absPath"])
  146. for res_frame, frame in zip(res_frames, frames):
  147. self.assertCompareData(
  148. res_frame,
  149. frame,
  150. ["lineNo", "function", "filename", "module", "context"],
  151. )
  152. if frame.get("vars"):
  153. self.assertCompareData(
  154. res_frame["vars"], frame["vars"], ["exc", "request"]
  155. )
  156. if frame["vars"].get("get_response"):
  157. # Memory address is different, truncate it
  158. self.assertEqual(
  159. res_frame["vars"]["get_response"][:-16],
  160. frame["vars"]["get_response"][:-16],
  161. )
  162. self.assertCompareData(
  163. res_data["entries"][0]["data"],
  164. sentry_data["entries"][0]["data"],
  165. ["env", "headers", "url", "method", "inferredContentType"],
  166. )
  167. url = reverse("api:get_issue", kwargs={"issue_id": event.issue.pk})
  168. res = self.client.get(url)
  169. self.assertEqual(res.status_code, 200)
  170. res_data = res.json()
  171. data = self.get_json_data("events/test_data/django_template_error_issue.json")
  172. self.assertCompareData(res_data, data, ["title", "metadata"])
  173. def test_js_sdk_with_unix_timestamp(self):
  174. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  175. "js_event_with_unix_timestamp"
  176. )
  177. event = self.submit_event(sdk_error)
  178. self.assertNotEqual(event.timestamp, sdk_error["timestamp"])
  179. self.assertEqual(event.timestamp.year, 2020)
  180. # event_json = self.get_event_json(event)
  181. # self.assertCompareData(event_json, sentry_json, ["datetime", "breadcrumbs"])
  182. # url = self.get_project_events_detail(event.pk)
  183. # res = self.client.get(url)
  184. # res_data = res.json()
  185. # self.assertCompareData(res_data, sentry_data, ["datetime"])
  186. # self.assertEqual(res_data["entries"][1].get("type"), "breadcrumbs")
  187. # self.assertEqual(
  188. # res_data["entries"][1],
  189. # self.upgrade_data(sentry_data["entries"][1]),
  190. # )