test_process_issue_event.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. import os
  2. import shutil
  3. import uuid
  4. from django.urls import reverse
  5. from model_bakery import baker
  6. from apps.issue_events.constants import EventStatus, LogLevel
  7. from apps.issue_events.models import Issue, IssueEvent, IssueHash
  8. from apps.projects.models import IssueEventProjectHourlyStatistic
  9. from apps.releases.models import Release
  10. from ..process_event import process_issue_events
  11. from ..schema import (
  12. CSPIssueEventSchema,
  13. ErrorIssueEventSchema,
  14. InterchangeIssueEvent,
  15. IssueEventSchema,
  16. SecuritySchema,
  17. )
  18. from .utils import EventIngestTestCase
  19. COMPAT_TEST_DATA_DIR = "events/test_data"
  20. def is_exception(v):
  21. return v.get("type") == "exception"
  22. class IssueEventIngestTestCase(EventIngestTestCase):
  23. """
  24. These tests bypass the API and celery. They test the event ingest logic itself.
  25. This file should be large are test the following use cases
  26. - Multiple event saved at the same time
  27. - Sentry API compatibility
  28. - Default, Error, and CSP types
  29. - Graceful failure such as duplicate event ids or invalid data
  30. """
  31. def test_two_events(self):
  32. with self.assertNumQueries(7):
  33. self.process_events([{}, {}])
  34. self.assertEqual(Issue.objects.count(), 1)
  35. self.assertEqual(IssueHash.objects.count(), 1)
  36. self.assertEqual(IssueEvent.objects.count(), 2)
  37. self.assertTrue(
  38. IssueEventProjectHourlyStatistic.objects.filter(
  39. count=2, project=self.project
  40. ).exists()
  41. )
  42. def test_two_issues(self):
  43. self.process_events(
  44. [
  45. {
  46. "message": "a",
  47. },
  48. {
  49. "message": "b",
  50. },
  51. ]
  52. )
  53. self.assertEqual(Issue.objects.count(), 2)
  54. self.assertEqual(IssueHash.objects.count(), 2)
  55. self.assertEqual(IssueEvent.objects.count(), 2)
  56. self.assertTrue(
  57. IssueEventProjectHourlyStatistic.objects.filter(
  58. count=2, project=self.project
  59. ).exists()
  60. )
  61. def test_transaction_truncation(self):
  62. long_string = "x" * 201
  63. truncated_string = "x" * 199 + "…"
  64. data = self.get_json_data("events/test_data/py_hi_event.json")
  65. data["culprit"] = long_string
  66. self.process_events(data)
  67. first_event = IssueEvent.objects.first()
  68. self.assertEqual(first_event.transaction, truncated_string)
  69. data = self.get_json_data("events/test_data/py_hi_event.json")
  70. data["transaction"] = long_string
  71. self.process_events(data)
  72. second_event = IssueEvent.objects.last()
  73. self.assertEqual(second_event.transaction, truncated_string)
  74. def test_message_empty_param_list(self):
  75. self.process_events(
  76. [
  77. {"logentry": {"message": "This is a warning: %s", "params": []}},
  78. ]
  79. )
  80. self.assertEqual(
  81. IssueEvent.objects.first().data["logentry"]["message"],
  82. "This is a warning: %s",
  83. )
  84. def test_query_release_environment_difs(self):
  85. """Test efficiency of existing release/environment/dif"""
  86. project2 = baker.make("projects.Project", organization=self.organization)
  87. release = baker.make("releases.Release", version="r", projects=[self.project])
  88. environment = baker.make(
  89. "environments.Environment", name="e", projects=[self.project]
  90. )
  91. baker.make("difs.DebugInformationFile", project=self.project)
  92. baker.make("releases.Release", projects=[self.project, project2])
  93. baker.make("releases.Release", version="r", projects=[project2])
  94. baker.make("releases.Release", version="r")
  95. baker.make("environments.Environment", projects=[self.project])
  96. baker.make("difs.DebugInformationFile", project=self.project)
  97. event1 = {
  98. "release": release.version,
  99. "environment": environment.name,
  100. }
  101. event2 = {
  102. "release": "newr",
  103. "environment": "newe",
  104. }
  105. with self.assertNumQueries(13):
  106. self.process_events([event1, {}])
  107. self.process_events([event1, event2, {}])
  108. self.assertEqual(self.project.releases.count(), 3)
  109. self.assertEqual(self.project.environment_set.count(), 3)
  110. def test_reopen_resolved_issue(self):
  111. event = self.process_events({})[0]
  112. issue = Issue.objects.first()
  113. issue.status = EventStatus.RESOLVED
  114. issue.save()
  115. event.event_id = uuid.uuid4()
  116. self.process_events(event.dict())
  117. issue.refresh_from_db()
  118. self.assertEqual(issue.status, EventStatus.UNRESOLVED)
  119. def test_fingerprint(self):
  120. data = {
  121. "exception": [
  122. {
  123. "type": "a",
  124. "value": "a",
  125. }
  126. ],
  127. "event_id": uuid.uuid4(),
  128. "fingerprint": ["foo", None, "bar"],
  129. }
  130. self.process_events(data)
  131. data["exception"][0]["type"] = "lol"
  132. data["event_id"] = uuid.uuid4()
  133. self.process_events(data)
  134. self.assertEqual(Issue.objects.count(), 1)
  135. self.assertEqual(IssueEvent.objects.count(), 2)
  136. def test_event_release(self):
  137. data = self.get_json_data("events/test_data/py_hi_event.json")
  138. baker.make("releases.Release", version=data.get("release"))
  139. self.process_events(data)
  140. event = IssueEvent.objects.first()
  141. self.assertTrue(event.release)
  142. self.assertTrue(
  143. Release.objects.filter(
  144. version=data.get("release"), projects=self.project
  145. ).exists()
  146. )
  147. def test_event_release_blank(self):
  148. """In the SDK, it's possible to set a release to a blank string"""
  149. data = self.get_json_data("events/test_data/py_hi_event.json")
  150. data["release"] = ""
  151. self.process_events(data)
  152. self.assertTrue(IssueEvent.objects.first())
  153. def test_event_environment(self):
  154. # Some noise to test queries
  155. baker.make("environments.Environment", organization=self.organization)
  156. baker.make("environments.EnvironmentProject", project=self.project)
  157. data = self.get_json_data("events/test_data/py_hi_event.json")
  158. data["environment"] = "dev"
  159. self.process_events(data)
  160. event = IssueEvent.objects.first()
  161. self.assertTrue(event.issue.project.environment_set.filter(name="dev").exists())
  162. self.assertEqual(event.issue.project.environment_set.count(), 2)
  163. data["event_id"] = uuid.uuid4().hex
  164. self.process_events(data)
  165. self.assertEqual(event.issue.project.environment_set.count(), 2)
  166. def test_multi_org_event_environment_processing(self):
  167. environment = baker.make(
  168. "environments.Environment", organization=self.organization, name="prod"
  169. )
  170. baker.make(
  171. "environments.EnvironmentProject",
  172. environment=environment,
  173. project=self.project,
  174. )
  175. event_list = []
  176. data = self.get_json_data("events/test_data/py_hi_event.json")
  177. data["environment"] = "dev"
  178. event_list.append(
  179. InterchangeIssueEvent(
  180. project_id=self.project.id,
  181. organization_id=self.organization.id,
  182. payload=IssueEventSchema(**data),
  183. )
  184. )
  185. org_b = baker.make("organizations_ext.organization")
  186. org_b_project = baker.make("projects.Project", organization=org_b)
  187. data = self.get_json_data("events/test_data/py_hi_event.json")
  188. data["environment"] = "prod"
  189. event_list.append(
  190. InterchangeIssueEvent(
  191. project_id=org_b_project.id,
  192. organization_id=org_b.id,
  193. payload=IssueEventSchema(**data),
  194. )
  195. )
  196. process_issue_events(event_list)
  197. self.assertTrue(self.project.environment_set.filter(name="dev").exists())
  198. self.assertEqual(self.project.environment_set.count(), 2)
  199. self.assertTrue(org_b_project.environment_set.filter(name="prod").exists())
  200. self.assertEqual(org_b_project.environment_set.count(), 1)
  201. def test_multi_org_event_release_processing(self):
  202. release = baker.make(
  203. "releases.Release", organization=self.organization, version="v1.0"
  204. )
  205. baker.make(
  206. "releases.ReleaseProject",
  207. release=release,
  208. project=self.project,
  209. )
  210. event_list = []
  211. data = self.get_json_data("events/test_data/py_hi_event.json")
  212. data["release"] = "v2.0"
  213. event_list.append(
  214. InterchangeIssueEvent(
  215. project_id=self.project.id,
  216. organization_id=self.organization.id,
  217. payload=IssueEventSchema(**data),
  218. )
  219. )
  220. org_b = baker.make("organizations_ext.organization")
  221. org_b_project = baker.make("projects.Project", organization=org_b)
  222. data = self.get_json_data("events/test_data/py_hi_event.json")
  223. data["release"] = "v1.0"
  224. event_list.append(
  225. InterchangeIssueEvent(
  226. project_id=org_b_project.id,
  227. organization_id=org_b.id,
  228. payload=IssueEventSchema(**data),
  229. )
  230. )
  231. process_issue_events(event_list)
  232. self.assertTrue(self.organization.release_set.filter(version="v2.0").exists())
  233. self.assertEqual(self.organization.release_set.count(), 2)
  234. self.assertTrue(org_b.release_set.filter(version="v1.0").exists())
  235. self.assertEqual(org_b.release_set.count(), 1)
  236. def test_process_sourcemap(self):
  237. sample_event = {
  238. "exception": {
  239. "values": [
  240. {
  241. "type": "Error",
  242. "value": "The error",
  243. "stacktrace": {
  244. "frames": [
  245. {
  246. "filename": "http://localhost:8080/dist/bundle.js",
  247. "function": "?",
  248. "in_app": True,
  249. "lineno": 2,
  250. "colno": 74016,
  251. },
  252. {
  253. "filename": "http://localhost:8080/dist/bundle.js",
  254. "function": "?",
  255. "in_app": True,
  256. "lineno": 2,
  257. "colno": 74012,
  258. },
  259. {
  260. "filename": "http://localhost:8080/dist/bundle.js",
  261. "function": "?",
  262. "in_app": True,
  263. "lineno": 2,
  264. "colno": 73992,
  265. },
  266. ]
  267. },
  268. "mechanism": {"type": "onerror", "handled": False},
  269. }
  270. ]
  271. },
  272. "level": "error",
  273. "platform": "javascript",
  274. "event_id": "0691751a89db419994efac8ac9b00a5d",
  275. "timestamp": 1648414309.82,
  276. "environment": "production",
  277. "request": {
  278. "url": "http://localhost:8080/",
  279. "headers": {
  280. "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0"
  281. },
  282. },
  283. }
  284. release = baker.make("releases.Release", organization=self.organization)
  285. release.projects.add(self.project)
  286. blob_bundle = baker.make("files.FileBlob", blob="uploads/file_blobs/bundle.js")
  287. blob_bundle_map = baker.make(
  288. "files.FileBlob", blob="uploads/file_blobs/bundle.js.map"
  289. )
  290. baker.make(
  291. "releases.ReleaseFile",
  292. release=release,
  293. file__name="bundle.js",
  294. file__blob=blob_bundle,
  295. )
  296. baker.make(
  297. "releases.ReleaseFile",
  298. release=release,
  299. file__name="bundle.js.map",
  300. file__blob=blob_bundle_map,
  301. )
  302. try:
  303. os.mkdir("./uploads/file_blobs")
  304. except FileExistsError:
  305. pass
  306. shutil.copyfile(
  307. "./apps/event_ingest/tests/test_data/bundle.js",
  308. "./uploads/file_blobs/bundle.js",
  309. )
  310. shutil.copyfile(
  311. "./apps/event_ingest/tests/test_data/bundle.js.map",
  312. "./uploads/file_blobs/bundle.js.map",
  313. )
  314. data = sample_event | {"release": release.version}
  315. self.process_events(data)
  316. # Show that colno changes
  317. self.assertEqual(
  318. IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][0][
  319. "colno"
  320. ],
  321. 13,
  322. )
  323. # Show that pre and post context is included
  324. self.assertEqual(
  325. len(
  326. IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][
  327. 0
  328. ]["pre_context"]
  329. ),
  330. 5,
  331. )
  332. self.assertEqual(
  333. len(
  334. IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][
  335. 0
  336. ]["post_context"]
  337. ),
  338. 1,
  339. )
  340. self.assertTrue(IssueEvent.objects.filter(release=release).exists())
  341. def test_search_vector(self):
  342. word = "orange"
  343. for _ in range(2):
  344. self.process_events([{"message": word}])
  345. issue = Issue.objects.filter(search_vector=word).first()
  346. self.assertTrue(issue)
  347. self.assertEqual(len(issue.search_vector.split(" ")), 1)
  348. def test_null_character_event(self):
  349. """
  350. Unicode null characters \u0000 are not supported by Postgres JSONB
  351. NUL \x00 characters are not supported by Postgres string types
  352. They should be filtered out
  353. """
  354. data = self.get_json_data("events/test_data/py_error.json")
  355. data["exception"]["values"][0]["stacktrace"]["frames"][0]["function"] = (
  356. "a\u0000a"
  357. )
  358. data["exception"]["values"][0]["value"] = "\x00\u0000"
  359. self.process_events(data)
  360. def test_csp_event_processing(self):
  361. self.create_logged_in_user()
  362. payload = self.get_json_data(
  363. "apps/event_ingest/tests/test_data/csp/mozilla_example.json"
  364. )
  365. data = SecuritySchema(**payload)
  366. event = CSPIssueEventSchema(csp=data.csp_report.dict(by_alias=True))
  367. process_issue_events(
  368. [
  369. InterchangeIssueEvent(
  370. project_id=self.project.id,
  371. organization_id=self.organization.id,
  372. payload=event.dict(by_alias=True),
  373. )
  374. ]
  375. )
  376. issue = Issue.objects.get()
  377. url = reverse("api:get_latest_issue_event", kwargs={"issue_id": issue.id})
  378. res = self.client.get(url)
  379. self.assertEqual(res.status_code, 200)
  380. self.assertEqual(res.json()["culprit"], "style-src-elem")
  381. def test_project_throttle_rate(self):
  382. self.project.event_throttle_rate = 100
  383. self.project.save()
  384. event_store_url = (
  385. reverse("api:event_store", args=[self.project.id])
  386. + "?sentry_key="
  387. + self.project.projectkey_set.first().public_key.hex
  388. )
  389. res = self.client.post(
  390. event_store_url,
  391. {"fake": "data"},
  392. content_type="application/json",
  393. )
  394. self.assertEqual(res.status_code, 429)
  395. class SentryCompatTestCase(EventIngestTestCase):
  396. """
  397. These tests specifically test former open source sentry compatibility
  398. But otherwise are part of issue event ingest testing
  399. """
  400. def setUp(self):
  401. super().setUp()
  402. self.create_logged_in_user()
  403. def get_json_test_data(self, name: str):
  404. """Get incoming event, sentry json, sentry api event"""
  405. event = self.get_json_data(
  406. f"{COMPAT_TEST_DATA_DIR}/incoming_events/{name}.json"
  407. )
  408. sentry_json = self.get_json_data(
  409. f"{COMPAT_TEST_DATA_DIR}/oss_sentry_json/{name}.json"
  410. )
  411. # Force captured test data to match test generated data
  412. sentry_json["project"] = self.project.id
  413. api_sentry_event = self.get_json_data(
  414. f"{COMPAT_TEST_DATA_DIR}/oss_sentry_events/{name}.json"
  415. )
  416. return event, sentry_json, api_sentry_event
  417. def get_event_json(self, event: IssueEvent):
  418. return self.client.get(
  419. reverse(
  420. "api:get_event_json",
  421. kwargs={
  422. "organization_slug": self.organization.slug,
  423. "issue_id": event.issue_id,
  424. "event_id": event.id,
  425. },
  426. )
  427. ).json()
  428. # Upgrade functions handle intentional differences between GlitchTip and Sentry OSS
  429. def upgrade_title(self, value: str):
  430. """Sentry OSS uses ... while GlitchTip uses unicode …"""
  431. if value[-1] == "…":
  432. return value[:-3]
  433. return value.strip("...")
  434. def upgrade_metadata(self, value: dict):
  435. value["title"] = self.upgrade_title(value["title"])
  436. return value
  437. def assertCompareData(self, data1: dict, data2: dict, fields: list[str]):
  438. """Compare data of two dict objects. Compare only provided fields list"""
  439. for field in fields:
  440. field_value1 = data1.get(field)
  441. field_value2 = data2.get(field)
  442. if field == "datetime":
  443. # Check that it's close enough
  444. field_value1 = field_value1[:23]
  445. field_value2 = field_value2[:23]
  446. if field == "title" and isinstance(field_value1, str):
  447. field_value1 = self.upgrade_title(field_value1)
  448. if field_value2:
  449. field_value2 = self.upgrade_title(field_value2)
  450. if (
  451. field == "metadata"
  452. and isinstance(field_value1, dict)
  453. and field_value1.get("title")
  454. ):
  455. field_value1 = self.upgrade_metadata(field_value1)
  456. if field_value2:
  457. field_value2 = self.upgrade_metadata(field_value2)
  458. self.assertEqual(
  459. field_value1,
  460. field_value2,
  461. f"Failed for field '{field}'",
  462. )
  463. def get_project_events_detail(self, event_id: str):
  464. return reverse(
  465. "api:get_project_issue_event",
  466. kwargs={
  467. "organization_slug": self.project.organization.slug,
  468. "project_slug": self.project.slug,
  469. "event_id": event_id,
  470. },
  471. )
  472. def submit_event(self, event_data: dict, event_type="error") -> IssueEvent:
  473. event_class = ErrorIssueEventSchema
  474. if event_type == "default":
  475. event_class = IssueEventSchema
  476. event = InterchangeIssueEvent(
  477. event_id=event_data["event_id"],
  478. organization_id=self.organization.id if self.organization else None,
  479. project_id=self.project.id,
  480. payload=event_class(**event_data),
  481. )
  482. process_issue_events([event])
  483. return IssueEvent.objects.get(pk=event.event_id)
  484. def upgrade_data(self, data):
  485. """A recursive replace function"""
  486. if isinstance(data, dict):
  487. return {k: self.upgrade_data(v) for k, v in data.items()}
  488. elif isinstance(data, list):
  489. return [self.upgrade_data(i) for i in data]
  490. return data
  491. def test_template_error(self):
  492. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  493. "django_template_error"
  494. )
  495. event = self.submit_event(sdk_error)
  496. url = self.get_project_events_detail(event.id.hex)
  497. res = self.client.get(url)
  498. res_data = res.json()
  499. self.assertEqual(res.status_code, 200)
  500. self.assertCompareData(res_data, sentry_data, ["culprit", "title", "metadata"])
  501. res_frames = res_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]
  502. frames = sentry_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]
  503. for i in range(6):
  504. # absPath don't always match - needs fixed
  505. self.assertCompareData(res_frames[i], frames[i], ["absPath"])
  506. for res_frame, frame in zip(res_frames, frames):
  507. self.assertCompareData(
  508. res_frame,
  509. frame,
  510. ["lineNo", "function", "filename", "module", "context"],
  511. )
  512. if frame.get("vars"):
  513. self.assertCompareData(
  514. res_frame["vars"], frame["vars"], ["exc", "request"]
  515. )
  516. if frame["vars"].get("get_response"):
  517. # Memory address is different, truncate it
  518. self.assertEqual(
  519. res_frame["vars"]["get_response"][:-16],
  520. frame["vars"]["get_response"][:-16],
  521. )
  522. self.assertCompareData(
  523. res_data["entries"][0]["data"],
  524. sentry_data["entries"][0]["data"],
  525. ["env", "headers", "url", "method", "inferredContentType"],
  526. )
  527. url = reverse("api:get_issue", kwargs={"issue_id": event.issue.pk})
  528. res = self.client.get(url)
  529. self.assertEqual(res.status_code, 200)
  530. res_data = res.json()
  531. data = self.get_json_data("events/test_data/django_template_error_issue.json")
  532. self.assertCompareData(res_data, data, ["title", "metadata"])
  533. def test_js_sdk_with_unix_timestamp(self):
  534. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  535. "js_event_with_unix_timestamp"
  536. )
  537. event = self.submit_event(sdk_error)
  538. self.assertNotEqual(event.timestamp, sdk_error["timestamp"])
  539. self.assertEqual(event.timestamp.year, 2020)
  540. event_json = self.get_event_json(event)
  541. self.assertCompareData(event_json, sentry_json, ["datetime"])
  542. res = self.client.get(self.get_project_events_detail(event.pk))
  543. res_data = res.json()
  544. self.assertCompareData(res_data, sentry_data, ["timestamp"])
  545. self.assertEqual(res_data["entries"][1].get("type"), "breadcrumbs")
  546. self.maxDiff = None
  547. self.assertEqual(
  548. res_data["entries"][1],
  549. self.upgrade_data(sentry_data["entries"][1]),
  550. )
  551. def test_dotnet_error(self):
  552. sdk_error = self.get_json_data(
  553. "events/test_data/incoming_events/dotnet_error.json"
  554. )
  555. event = self.submit_event(sdk_error)
  556. self.assertEqual(IssueEvent.objects.count(), 1)
  557. sentry_data = self.get_json_data(
  558. "events/test_data/oss_sentry_events/dotnet_error.json"
  559. )
  560. res = self.client.get(self.get_project_events_detail(event.pk))
  561. res_data = res.json()
  562. self.assertCompareData(
  563. res_data,
  564. sentry_data,
  565. ["eventID", "title", "culprit", "platform", "type", "metadata"],
  566. )
  567. res_exception = next(filter(is_exception, res_data["entries"]), None)
  568. sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
  569. self.assertEqual(
  570. res_exception["data"].get("hasSystemFrames"),
  571. sentry_exception["data"].get("hasSystemFrames"),
  572. )
  573. def test_php_message_event(self):
  574. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  575. "php_message_event"
  576. )
  577. event = self.submit_event(sdk_error)
  578. res = self.client.get(self.get_project_events_detail(event.pk))
  579. res_data = res.json()
  580. self.assertCompareData(
  581. res_data,
  582. sentry_data,
  583. [
  584. "message",
  585. "title",
  586. ],
  587. )
  588. self.assertEqual(
  589. res_data["entries"][0]["data"]["params"],
  590. sentry_data["entries"][0]["data"]["params"],
  591. )
  592. def test_django_message_params(self):
  593. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  594. "django_message_params"
  595. )
  596. event = self.submit_event(sdk_error)
  597. res = self.client.get(self.get_project_events_detail(event.pk))
  598. res_data = res.json()
  599. self.assertCompareData(
  600. res_data,
  601. sentry_data,
  602. [
  603. "message",
  604. "title",
  605. ],
  606. )
  607. self.assertEqual(res_data["entries"][0], sentry_data["entries"][0])
  608. def test_message_event(self):
  609. """A generic message made with the Sentry SDK. Generally has less data than exceptions."""
  610. from events.test_data.django_error_factory import message
  611. event = self.submit_event(message, event_type="default")
  612. res = self.client.get(self.get_project_events_detail(event.pk))
  613. res_data = res.json()
  614. data = self.get_json_data("events/test_data/django_message_event.json")
  615. self.assertCompareData(
  616. res_data,
  617. data,
  618. ["title", "culprit", "type", "metadata", "platform", "packages"],
  619. )
  620. def test_python_logging(self):
  621. """Test Sentry SDK logging integration based event"""
  622. sdk_error, sentry_json, sentry_data = self.get_json_test_data("python_logging")
  623. event = self.submit_event(sdk_error, event_type="default")
  624. res = self.client.get(self.get_project_events_detail(event.pk))
  625. res_data = res.json()
  626. self.assertEqual(res.status_code, 200)
  627. self.assertCompareData(
  628. res_data,
  629. sentry_data,
  630. [
  631. "title",
  632. "logentry",
  633. "culprit",
  634. "type",
  635. "metadata",
  636. "platform",
  637. "packages",
  638. ],
  639. )
  640. def test_go_file_not_found(self):
  641. sdk_error = self.get_json_data(
  642. "events/test_data/incoming_events/go_file_not_found.json"
  643. )
  644. event = self.submit_event(sdk_error)
  645. sentry_data = self.get_json_data(
  646. "events/test_data/oss_sentry_events/go_file_not_found.json"
  647. )
  648. res = self.client.get(self.get_project_events_detail(event.pk))
  649. res_data = res.json()
  650. self.assertEqual(res.status_code, 200)
  651. self.assertCompareData(
  652. res_data,
  653. sentry_data,
  654. ["title", "culprit", "type", "metadata", "platform"],
  655. )
  656. def test_very_small_event(self):
  657. """
  658. Shows a very minimalist event example. Good for seeing what data is null
  659. """
  660. sdk_error = self.get_json_data(
  661. "events/test_data/incoming_events/very_small_event.json"
  662. )
  663. event = self.submit_event(sdk_error, event_type="default")
  664. sentry_data = self.get_json_data(
  665. "events/test_data/oss_sentry_events/very_small_event.json"
  666. )
  667. res = self.client.get(self.get_project_events_detail(event.pk))
  668. res_data = res.json()
  669. self.assertEqual(res.status_code, 200)
  670. self.assertCompareData(
  671. res_data,
  672. sentry_data,
  673. ["culprit", "type", "platform", "entries"],
  674. )
  675. def test_python_zero_division(self):
  676. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  677. "python_zero_division"
  678. )
  679. event = self.submit_event(sdk_error)
  680. event_json = self.get_event_json(event)
  681. self.assertCompareData(
  682. event_json,
  683. sentry_json,
  684. [
  685. "event_id",
  686. "project",
  687. "release",
  688. "dist",
  689. "platform",
  690. "level",
  691. "modules",
  692. "time_spent",
  693. "sdk",
  694. "type",
  695. "title",
  696. "breadcrumbs",
  697. ],
  698. )
  699. self.assertCompareData(
  700. event_json["request"],
  701. sentry_json["request"],
  702. [
  703. "url",
  704. "headers",
  705. "method",
  706. "env",
  707. "query_string",
  708. ],
  709. )
  710. self.assertEqual(
  711. event_json["datetime"][:22],
  712. sentry_json["datetime"][:22],
  713. "Compare if datetime is almost the same",
  714. )
  715. res = self.client.get(self.get_project_events_detail(event.pk))
  716. res_data = res.json()
  717. self.assertEqual(res.status_code, 200)
  718. self.assertCompareData(
  719. res_data,
  720. sentry_data,
  721. ["title", "culprit", "type", "metadata", "platform", "packages"],
  722. )
  723. self.assertCompareData(
  724. res_data["entries"][1]["data"],
  725. sentry_data["entries"][1]["data"],
  726. [
  727. "inferredContentType",
  728. "env",
  729. "headers",
  730. "url",
  731. "query",
  732. "data",
  733. "method",
  734. ],
  735. )
  736. issue = event.issue
  737. issue.refresh_from_db()
  738. self.assertEqual(issue.level, LogLevel.ERROR)
  739. def test_dotnet_zero_division(self):
  740. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  741. "dotnet_divide_zero"
  742. )
  743. event = self.submit_event(sdk_error)
  744. event_json = self.get_event_json(event)
  745. res = self.client.get(self.get_project_events_detail(event.pk))
  746. res_data = res.json()
  747. self.assertCompareData(event_json, sentry_json, ["environment"])
  748. self.assertCompareData(
  749. res_data,
  750. sentry_data,
  751. [
  752. "eventID",
  753. "title",
  754. "culprit",
  755. "platform",
  756. "type",
  757. "metadata",
  758. ],
  759. )
  760. res_exception = next(filter(is_exception, res_data["entries"]), None)
  761. sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
  762. self.assertEqual(
  763. res_exception["data"]["values"][0]["stacktrace"]["frames"][4]["context"],
  764. sentry_exception["data"]["values"][0]["stacktrace"]["frames"][4]["context"],
  765. )
  766. tags = res_data.get("tags")
  767. browser_tag = next(filter(lambda tag: tag["key"] == "browser", tags), None)
  768. self.assertEqual(browser_tag["value"], "Firefox 76.0")
  769. environment_tag = next(
  770. filter(lambda tag: tag["key"] == "environment", tags), None
  771. )
  772. self.assertEqual(environment_tag["value"], "Development")
  773. def test_ruby_zero_division(self):
  774. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  775. "ruby_zero_division"
  776. )
  777. event = self.submit_event(sdk_error)
  778. event_json = self.get_event_json(event)
  779. res = self.client.get(self.get_project_events_detail(event.pk))
  780. res_data = res.json()
  781. res_exception = next(filter(is_exception, res_data["entries"]), None)
  782. sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
  783. self.assertEqual(
  784. res_exception["data"]["values"][0]["stacktrace"]["frames"][-1]["context"],
  785. sentry_exception["data"]["values"][0]["stacktrace"]["frames"][-1][
  786. "context"
  787. ],
  788. )
  789. self.assertCompareData(event_json, sentry_json, ["environment"])
  790. self.assertCompareData(
  791. res_data,
  792. sentry_data,
  793. [
  794. "eventID",
  795. "title",
  796. "culprit",
  797. "platform",
  798. "type",
  799. "metadata",
  800. ],
  801. )
  802. def test_sentry_cli_send_event_no_level(self):
  803. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  804. "sentry_cli_send_event_no_level"
  805. )
  806. event = self.submit_event(sdk_error, event_type="default")
  807. event_json = self.get_event_json(event)
  808. self.assertCompareData(event_json, sentry_json, ["title"])
  809. self.assertEqual(event_json["project"], event.issue.project_id)
  810. res = self.client.get(self.get_project_events_detail(event.pk))
  811. res_data = res.json()
  812. self.assertCompareData(
  813. res_data,
  814. sentry_data,
  815. [
  816. "userReport",
  817. "title",
  818. "culprit",
  819. "type",
  820. "metadata",
  821. "message",
  822. "platform",
  823. "previousEventID",
  824. ],
  825. )
  826. self.assertEqual(res_data["projectID"], event.issue.project_id)
  827. def test_js_error_with_context(self):
  828. self.project.scrub_ip_addresses = False
  829. self.project.save()
  830. sdk_error, sentry_json, sentry_data = self.get_json_test_data(
  831. "js_error_with_context"
  832. )
  833. event_store_url = (
  834. reverse("api:event_store", args=[self.project.id])
  835. + "?sentry_key="
  836. + self.project.projectkey_set.first().public_key.hex
  837. )
  838. res = self.client.post(
  839. event_store_url,
  840. sdk_error,
  841. content_type="application/json",
  842. REMOTE_ADDR="142.255.29.14",
  843. )
  844. res_data = res.json()
  845. event = IssueEvent.objects.get(pk=res_data["event_id"])
  846. event_json = self.get_event_json(event)
  847. self.assertCompareData(event_json, sentry_json, ["title", "extra", "user"])
  848. url = self.get_project_events_detail(event.pk)
  849. res = self.client.get(url)
  850. res_json = res.json()
  851. self.assertCompareData(res_json, sentry_data, ["context"])
  852. self.assertCompareData(
  853. res_json["user"], sentry_data["user"], ["id", "email", "ip_address"]
  854. )
  855. def test_small_js_error(self):
  856. """A small example to test stacktraces"""
  857. sdk_error, sentry_json, sentry_data = self.get_json_test_data("small_js_error")
  858. event = self.submit_event(sdk_error, event_type="default")
  859. event_json = self.get_event_json(event)
  860. self.assertCompareData(
  861. event_json["exception"][0],
  862. sentry_json["exception"]["values"][0],
  863. ["type", "values", "exception", "abs_path"],
  864. )