test_sourcemap_workflow.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import gzip
  2. import hashlib
  3. import io
  4. import json
  5. import uuid
  6. import zipfile
  7. from django.core.files.uploadedfile import SimpleUploadedFile
  8. from django.http.response import HttpResponse
  9. from django.urls import reverse
  10. from apps.event_ingest.tests.utils import generate_event
  11. from apps.files.models import File, FileBlob
  12. from apps.issue_events.models import Issue
  13. from glitchtip.test_utils.test_case import GlitchTestCase
  14. debug_id = str(uuid.uuid4())
  15. minified_js = "function a(n,t){return n+t}"
  16. minified_js_map = f"""{{
  17. "version": 3,
  18. "file": "minified.js",
  19. "sourceRoot": "",
  20. "sources": ["original.js"],
  21. "names": ["calculateSum", "firstNumber", "secondNumber", "a", "n", "t"],
  22. "mappings": "AAAA,QAASA,CAAT,CAAeC,CAAf,EAAkBC,CAAlB,EAAqB,OAAOD,CAAP,GAAUC,CAAV,GAAaC,CAAd",
  23. "sourcesContent": ["function calculateSum(firstNumber, secondNumber) {{\\n return firstNumber + secondNumber;\\n}}"],
  24. "debugId": "{debug_id}"
  25. }}"""
  26. original_js = """function calculateSum(firstNumber, secondNumber) {
  27. return firstNumber + secondNumber;
  28. }"""
  29. class SourceCodeTestCase(GlitchTestCase):
  30. @classmethod
  31. def setUpTestData(cls):
  32. cls.create_user()
  33. def setUp(self):
  34. self.client.force_login(self.user)
  35. def upload_chunk(
  36. self,
  37. url: str,
  38. ) -> tuple[HttpResponse, str]:
  39. # First construct zip file containing source, map, and manifest files.
  40. manifest = {
  41. "files": {
  42. f"files/_/_/{debug_id}-0.js": {
  43. "type": "minified_source",
  44. "url": f"~/{debug_id}-0.js",
  45. "headers": {"debug-id": debug_id, "sourcemap": "minified.js.map"},
  46. },
  47. f"files/_/_/{debug_id}-0.js.map": {
  48. "type": "source_map",
  49. "url": f"~/{debug_id}-0.js.map",
  50. "headers": {"debug-id": debug_id},
  51. },
  52. },
  53. "debug_id": str(uuid.uuid4()),
  54. "org": self.organization.slug,
  55. "project": self.project.slug,
  56. }
  57. in_memory_buffer = io.BytesIO()
  58. with zipfile.ZipFile(in_memory_buffer, mode="w") as zipf:
  59. zipf.writestr("manifest.json", json.dumps(manifest))
  60. zipf.writestr(f"files/_/_/{debug_id}-0.js", minified_js)
  61. zipf.writestr(f"files/_/_/{debug_id}-0.js.map", minified_js_map)
  62. in_memory_buffer.seek(0)
  63. # Calculate the SHA1 checksum first
  64. checksum = hashlib.sha1(in_memory_buffer.read()).hexdigest()
  65. in_memory_buffer.seek(0)
  66. file = SimpleUploadedFile(
  67. checksum, # Use checksum as filename
  68. gzip.compress(in_memory_buffer.read()),
  69. )
  70. response = self.client.post(
  71. url,
  72. {"file_gzip": [file]},
  73. )
  74. return response, checksum
  75. def test_sourcemap_integrated(self):
  76. """Test full workflow of uploading sourcemaps to unminifying event code"""
  77. chunk_upload_url = reverse(
  78. "api:get_chunk_upload_info", args=[self.organization.slug]
  79. )
  80. assemble_url = reverse(
  81. "api:artifact_bundle_assemble", args=[self.organization.slug]
  82. )
  83. envelope_url = (
  84. reverse("api:event_envelope", args=[self.project.id])
  85. + f"?sentry_key={self.projectkey.public_key}"
  86. )
  87. res = self.client.get(chunk_upload_url)
  88. self.assertContains(res, "artifact_bundles") # sentry sdk requires this set
  89. # Upload source code "chunks"
  90. res, checksum = self.upload_chunk(chunk_upload_url)
  91. self.assertEqual(res.status_code, 200)
  92. self.assertEqual(FileBlob.objects.count(), 1)
  93. # Call assemble
  94. res = self.client.post(
  95. assemble_url,
  96. {
  97. "checksum": checksum,
  98. "chunks": [checksum],
  99. "projects": [self.project.slug],
  100. },
  101. content_type="application/json",
  102. )
  103. self.assertContains(res, "created")
  104. self.assertEqual(FileBlob.objects.count(), 3)
  105. self.assertEqual(File.objects.count(), 2)
  106. # Submit event
  107. data = generate_event(
  108. event_type="error",
  109. platform="javascript",
  110. event={
  111. "exception": {
  112. "values": [
  113. {
  114. "type": "Error",
  115. "value": "err",
  116. "stacktrace": {
  117. "frames": [
  118. {
  119. "filename": "http://127.0.0.1:8080/assets/minified.js",
  120. "function": "?",
  121. "in_app": True,
  122. "lineno": 2,
  123. "colno": 4,
  124. },
  125. ]
  126. },
  127. }
  128. ]
  129. },
  130. "debug_meta": {
  131. "images": [
  132. {
  133. "type": "sourcemap",
  134. "code_file": "http://127.0.0.1:8080/assets/minified.js",
  135. "debug_id": debug_id,
  136. }
  137. ]
  138. },
  139. },
  140. envelope=True,
  141. )
  142. res = self.client.post(envelope_url, data, content_type="application/json")
  143. self.assertContains(res, data[0]["event_id"][:8])
  144. self.assertEqual(Issue.objects.count(), 1)
  145. issue = Issue.objects.get()
  146. event = issue.issueevent_set.first()
  147. self.assertIn(
  148. "firstNumber",
  149. event.data["exception"][0]["stacktrace"]["frames"][0]["context_line"],
  150. )