test_process_issue_event.py 35 KB

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