test_plugin.py 104 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743
  1. import os.path
  2. import zipfile
  3. from base64 import b64encode
  4. from io import BytesIO
  5. from unittest.mock import patch
  6. from uuid import uuid4
  7. import pytest
  8. import responses
  9. from django.utils.encoding import force_bytes
  10. from sentry.models import (
  11. ArtifactBundle,
  12. DebugIdArtifactBundle,
  13. File,
  14. ProjectArtifactBundle,
  15. Release,
  16. ReleaseArtifactBundle,
  17. ReleaseFile,
  18. SourceFileType,
  19. )
  20. from sentry.models.releasefile import update_artifact_index
  21. from sentry.testutils import RelayStoreHelper
  22. from sentry.testutils.helpers.datetime import before_now, iso_format
  23. from sentry.testutils.skips import requires_symbolicator
  24. from sentry.utils import json
  25. # IMPORTANT:
  26. #
  27. # This test suite requires Symbolicator in order to run correctly.
  28. # Set `symbolicator.enabled: true` in your `~/.sentry/config.yml` and run `sentry devservices up`
  29. #
  30. # If you are using a local instance of Symbolicator, you need to either change `system.url-prefix` to `system.internal-url-prefix`
  31. # inside `process_with_symbolicator` fixture inside `src/sentry/utils/pytest/fixtures.py`,
  32. # or add `127.0.0.1 host.docker.internal` entry to your `/etc/hosts`
  33. BASE64_SOURCEMAP = "data:application/json;base64," + (
  34. b64encode(
  35. b'{"version":3,"file":"generated.js","sources":["/test.js"],"names":[],"mappings":"AAAA","sourcesContent":['
  36. b'"console.log(\\"hello, World!\\")"]}'
  37. )
  38. .decode("utf-8")
  39. .replace("\n", "")
  40. )
  41. INVALID_BASE64_SOURCEMAP = "data:application/json;base64,A"
  42. def get_fixture_path(name):
  43. return os.path.join(os.path.dirname(__file__), "fixtures", name)
  44. def load_fixture(name):
  45. with open(get_fixture_path(name), "rb") as fp:
  46. return fp.read()
  47. @pytest.mark.django_db(transaction=True)
  48. class TestJavascriptIntegration(RelayStoreHelper):
  49. @pytest.fixture(autouse=True)
  50. def initialize(self, default_projectkey, default_project):
  51. self.project = default_project
  52. self.projectkey = default_projectkey
  53. self.organization = self.project.organization
  54. self.min_ago = iso_format(before_now(minutes=1))
  55. # We disable scraping per-test when necessary.
  56. self.project.update_option("sentry:scrape_javascript", True)
  57. @requires_symbolicator
  58. @pytest.mark.symbolicator
  59. def test_adds_contexts_without_device(self, process_with_symbolicator):
  60. data = {
  61. "timestamp": self.min_ago,
  62. "message": "hello",
  63. "platform": "javascript",
  64. "request": {
  65. "url": "http://example.com",
  66. "headers": [
  67. [
  68. "User-Agent",
  69. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
  70. "Chrome/28.0.1500.72 Safari/537.36",
  71. ]
  72. ],
  73. },
  74. }
  75. event = self.post_and_retrieve_event(data)
  76. contexts = event.interfaces["contexts"].to_json()
  77. assert contexts.get("os") == {"name": "Windows", "version": "8", "type": "os"}
  78. assert contexts.get("device") is None
  79. @requires_symbolicator
  80. @pytest.mark.symbolicator
  81. @pytest.mark.skip(
  82. reason="temp disable test due to circular dependency before merging PR: https://github.com/getsentry/relay/pull/2004"
  83. )
  84. def test_adds_contexts_with_device(self, process_with_symbolicator):
  85. data = {
  86. "timestamp": self.min_ago,
  87. "message": "hello",
  88. "platform": "javascript",
  89. "request": {
  90. "url": "http://example.com",
  91. "headers": [
  92. [
  93. "User-Agent",
  94. "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SCH-R530U Build/JSS15J) AppleWebKit/534.30 ("
  95. "KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 USCC-R530U",
  96. ]
  97. ],
  98. },
  99. }
  100. event = self.post_and_retrieve_event(data)
  101. contexts = event.interfaces["contexts"].to_json()
  102. assert contexts.get("os") == {"name": "Android", "type": "os", "version": "4.3"}
  103. assert contexts.get("browser") == {"name": "Android", "type": "browser", "version": "4.3"}
  104. assert contexts.get("device") == {
  105. "family": "Samsung SCH-R530U",
  106. "type": "device",
  107. "model": "SCH-R530U",
  108. "brand": "Samsung",
  109. }
  110. @requires_symbolicator
  111. @pytest.mark.symbolicator
  112. def test_adds_contexts_with_ps4_device(self, process_with_symbolicator):
  113. data = {
  114. "timestamp": self.min_ago,
  115. "message": "hello",
  116. "platform": "javascript",
  117. "request": {
  118. "url": "http://example.com",
  119. "headers": [
  120. [
  121. "User-Agent",
  122. "Mozilla/5.0 (PlayStation 4 3.55) AppleWebKit/537.78 (KHTML, like Gecko)",
  123. ]
  124. ],
  125. },
  126. }
  127. event = self.post_and_retrieve_event(data)
  128. contexts = event.interfaces["contexts"].to_json()
  129. assert contexts.get("os") is None
  130. assert contexts.get("browser") is None
  131. assert contexts.get("device") == {
  132. "family": "PlayStation 4",
  133. "type": "device",
  134. "model": "PlayStation 4",
  135. "brand": "Sony",
  136. }
  137. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  138. def test_source_expansion(self, mock_fetch_by_url):
  139. data = {
  140. "timestamp": self.min_ago,
  141. "message": "hello",
  142. "platform": "javascript",
  143. "exception": {
  144. "values": [
  145. {
  146. "type": "Error",
  147. "stacktrace": {
  148. "frames": [
  149. {
  150. "abs_path": "http://example.com/foo.js",
  151. "filename": "foo.js",
  152. "lineno": 4,
  153. "colno": 0,
  154. },
  155. {
  156. "abs_path": "http://example.com/foo.js",
  157. "filename": "foo.js",
  158. "lineno": 1,
  159. "colno": 0,
  160. },
  161. ]
  162. },
  163. }
  164. ]
  165. },
  166. }
  167. mock_fetch_by_url.return_value.body = force_bytes("\n".join("hello world"))
  168. mock_fetch_by_url.return_value.encoding = None
  169. mock_fetch_by_url.return_value.headers = {}
  170. event = self.post_and_retrieve_event(data)
  171. mock_fetch_by_url.assert_called_once_with("http://example.com/foo.js")
  172. exception = event.interfaces["exception"]
  173. frame_list = exception.values[0].stacktrace.frames
  174. frame = frame_list[0]
  175. assert frame.pre_context == ["h", "e", "l"]
  176. assert frame.context_line == "l"
  177. assert frame.post_context == ["o", " ", "w", "o", "r"]
  178. frame = frame_list[1]
  179. assert not frame.pre_context
  180. assert frame.context_line == "h"
  181. assert frame.post_context == ["e", "l", "l", "o", " "]
  182. # no source map means no raw_stacktrace
  183. assert exception.values[0].raw_stacktrace is None
  184. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  185. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  186. def test_inlined_sources(self, mock_discover_sourcemap, mock_fetch_by_url):
  187. data = {
  188. "timestamp": self.min_ago,
  189. "message": "hello",
  190. "platform": "javascript",
  191. "exception": {
  192. "values": [
  193. {
  194. "type": "Error",
  195. "stacktrace": {
  196. "frames": [
  197. {
  198. "abs_path": "http://example.com/test.min.js",
  199. "filename": "test.js",
  200. "lineno": 1,
  201. "colno": 1,
  202. }
  203. ]
  204. },
  205. }
  206. ]
  207. },
  208. }
  209. mock_discover_sourcemap.return_value = BASE64_SOURCEMAP
  210. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  211. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  212. mock_fetch_by_url.return_value.encoding = None
  213. event = self.post_and_retrieve_event(data)
  214. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  215. exception = event.interfaces["exception"]
  216. frame_list = exception.values[0].stacktrace.frames
  217. frame = frame_list[0]
  218. assert not frame.pre_context
  219. assert frame.context_line == 'console.log("hello, World!")'
  220. assert not frame.post_context
  221. assert frame.data["sourcemap"] == "http://example.com/test.min.js"
  222. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  223. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  224. def test_invalid_base64_sourcemap_returns_an_error(
  225. self, mock_discover_sourcemap, mock_fetch_by_url
  226. ):
  227. data = {
  228. "timestamp": self.min_ago,
  229. "message": "hello",
  230. "platform": "javascript",
  231. "exception": {
  232. "values": [
  233. {
  234. "type": "Error",
  235. "stacktrace": {
  236. "frames": [
  237. {
  238. "abs_path": "http://example.com/test.min.js",
  239. "filename": "test.js",
  240. "lineno": 1,
  241. "colno": 1,
  242. },
  243. ]
  244. },
  245. }
  246. ]
  247. },
  248. }
  249. mock_discover_sourcemap.return_value = INVALID_BASE64_SOURCEMAP
  250. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  251. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  252. mock_fetch_by_url.return_value.encoding = None
  253. event = self.post_and_retrieve_event(data)
  254. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  255. assert len(event.data["errors"]) == 1
  256. assert event.data["errors"][0] == {
  257. "url": "<base64>",
  258. "reason": "Invalid base64-encoded string: "
  259. "number of data characters (1) cannot be 1 more than a multiple of 4",
  260. "type": "js_invalid_source",
  261. }
  262. @patch("sentry.lang.javascript.processor.SmCache.from_bytes")
  263. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  264. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  265. def test_sourcemap_cache_is_constructed_only_once_if_an_error_is_raised(
  266. self, mock_discover_sourcemap, mock_fetch_by_url, mock_from_bytes
  267. ):
  268. data = {
  269. "timestamp": self.min_ago,
  270. "message": "hello",
  271. "platform": "javascript",
  272. "exception": {
  273. "values": [
  274. {
  275. "type": "Error",
  276. "stacktrace": {
  277. "frames": [
  278. {
  279. "abs_path": "http://example.com/test.min.js",
  280. "filename": "test.js",
  281. "lineno": 1,
  282. "colno": 1,
  283. },
  284. {
  285. "abs_path": "http://example.com/test.min.js",
  286. "filename": "test.js",
  287. "lineno": 1,
  288. "colno": 1,
  289. },
  290. {
  291. "abs_path": "http://example.com/test.min.js",
  292. "filename": "test.js",
  293. "lineno": 1,
  294. "colno": 1,
  295. },
  296. ]
  297. },
  298. }
  299. ]
  300. },
  301. }
  302. mock_discover_sourcemap.return_value = BASE64_SOURCEMAP
  303. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  304. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  305. mock_fetch_by_url.return_value.encoding = None
  306. mock_from_bytes.side_effect = Exception()
  307. self.post_and_retrieve_event(data)
  308. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  309. mock_from_bytes.assert_called_once()
  310. @requires_symbolicator
  311. @pytest.mark.symbolicator
  312. def test_error_message_translations(self, process_with_symbolicator):
  313. data = {
  314. "timestamp": self.min_ago,
  315. "message": "hello",
  316. "platform": "javascript",
  317. "logentry": {
  318. "formatted": "ReferenceError: Impossible de d\xe9finir une propri\xe9t\xe9 \xab foo \xbb : objet non "
  319. "extensible"
  320. },
  321. "exception": {
  322. "values": [
  323. {"type": "Error", "value": "P\u0159\xedli\u0161 mnoho soubor\u016f"},
  324. {
  325. "type": "Error",
  326. "value": "foo: wyst\u0105pi\u0142 nieoczekiwany b\u0142\u0105d podczas pr\xf3by uzyskania "
  327. "informacji o metadanych",
  328. },
  329. ]
  330. },
  331. }
  332. event = self.post_and_retrieve_event(data)
  333. message = event.interfaces["logentry"]
  334. assert (
  335. message.formatted
  336. == "ReferenceError: Cannot define property 'foo': object is not extensible"
  337. )
  338. exception = event.interfaces["exception"]
  339. assert exception.values[0].value == "Too many files"
  340. assert (
  341. exception.values[1].value
  342. == "foo: an unexpected failure occurred while trying to obtain metadata information"
  343. )
  344. @requires_symbolicator
  345. @pytest.mark.symbolicator
  346. def test_sourcemap_source_expansion(self, process_with_symbolicator):
  347. self.project.update_option("sentry:scrape_javascript", False)
  348. release = Release.objects.create(
  349. organization_id=self.project.organization_id, version="abc"
  350. )
  351. release.add_project(self.project)
  352. for file in ["file.min.js", "file1.js", "file2.js", "file.sourcemap.js"]:
  353. with open(get_fixture_path(file), "rb") as f:
  354. f1 = File.objects.create(
  355. name=file,
  356. type="release.file",
  357. headers={},
  358. )
  359. f1.putfile(f)
  360. ReleaseFile.objects.create(
  361. name=f"http://example.com/{f1.name}",
  362. release_id=release.id,
  363. organization_id=self.project.organization_id,
  364. file=f1,
  365. )
  366. data = {
  367. "timestamp": self.min_ago,
  368. "message": "hello",
  369. "platform": "javascript",
  370. "release": "abc",
  371. "exception": {
  372. "values": [
  373. {
  374. "type": "Error",
  375. "stacktrace": {
  376. "frames": [
  377. {
  378. "abs_path": "http://example.com/file.min.js",
  379. "filename": "file.min.js",
  380. "lineno": 1,
  381. "colno": 39,
  382. },
  383. # NOTE: Intentionally source is not retrieved from this HTML file
  384. {
  385. "function": 'function: "HTMLDocument.<anonymous>"',
  386. "abs_path": "http//example.com/index.html",
  387. "filename": "index.html",
  388. "lineno": 283,
  389. "colno": 17,
  390. "in_app": False,
  391. },
  392. ]
  393. },
  394. }
  395. ]
  396. },
  397. }
  398. event = self.post_and_retrieve_event(data)
  399. assert event.data["errors"] == [
  400. {"type": "js_no_source", "url": "http//example.com/index.html"}
  401. ]
  402. exception = event.interfaces["exception"]
  403. frame_list = exception.values[0].stacktrace.frames
  404. frame = frame_list[0]
  405. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  406. expected = "\treturn a + b; // fôo"
  407. assert frame.context_line == expected
  408. if process_with_symbolicator:
  409. assert frame.post_context == ["}"]
  410. else:
  411. assert frame.post_context == ["}", ""]
  412. raw_frame_list = exception.values[0].raw_stacktrace.frames
  413. raw_frame = raw_frame_list[0]
  414. assert not raw_frame.pre_context
  415. assert (
  416. raw_frame.context_line
  417. == 'function add(a,b){"use strict";return a+b}function multiply(a,b){"use strict";return a*b}function '
  418. 'divide(a,b){"use strict";try{return multip {snip}'
  419. )
  420. if process_with_symbolicator:
  421. assert raw_frame.post_context == ["//@ sourceMappingURL=file.sourcemap.js"]
  422. else:
  423. assert raw_frame.post_context == ["//@ sourceMappingURL=file.sourcemap.js", ""]
  424. assert raw_frame.lineno == 1
  425. # Since we couldn't expand source for the 2nd frame, both
  426. # its raw and original form should be identical
  427. assert raw_frame_list[1] == frame_list[1]
  428. @requires_symbolicator
  429. @pytest.mark.symbolicator
  430. def test_sourcemap_embedded_source_expansion(self, process_with_symbolicator):
  431. self.project.update_option("sentry:scrape_javascript", False)
  432. release = Release.objects.create(
  433. organization_id=self.project.organization_id, version="abc"
  434. )
  435. release.add_project(self.project)
  436. for file in ["embedded.js", "embedded.js.map"]:
  437. with open(get_fixture_path(file), "rb") as f:
  438. f1 = File.objects.create(
  439. name=file,
  440. type="release.file",
  441. headers={},
  442. )
  443. f1.putfile(f)
  444. ReleaseFile.objects.create(
  445. name=f"http://example.com/{f1.name}",
  446. release_id=release.id,
  447. organization_id=self.project.organization_id,
  448. file=f1,
  449. )
  450. data = {
  451. "timestamp": self.min_ago,
  452. "message": "hello",
  453. "platform": "javascript",
  454. "release": "abc",
  455. "exception": {
  456. "values": [
  457. {
  458. "type": "Error",
  459. "stacktrace": {
  460. "frames": [
  461. {
  462. "abs_path": "http://example.com/embedded.js",
  463. "filename": "file.min.js",
  464. "lineno": 1,
  465. "colno": 39,
  466. },
  467. # NOTE: Intentionally source is not retrieved from this HTML file
  468. {
  469. "function": 'function: "HTMLDocument.<anonymous>"',
  470. "abs_path": "http//example.com/index.html",
  471. "filename": "index.html",
  472. "lineno": 283,
  473. "colno": 17,
  474. "in_app": False,
  475. },
  476. ]
  477. },
  478. }
  479. ]
  480. },
  481. }
  482. event = self.post_and_retrieve_event(data)
  483. assert event.data["errors"] == [
  484. {"type": "js_no_source", "url": "http//example.com/index.html"}
  485. ]
  486. exception = event.interfaces["exception"]
  487. frame_list = exception.values[0].stacktrace.frames
  488. frame = frame_list[0]
  489. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  490. expected = "\treturn a + b; // fôo"
  491. assert frame.context_line == expected
  492. if process_with_symbolicator:
  493. assert frame.post_context == ["}"]
  494. else:
  495. assert frame.post_context == ["}", ""]
  496. @requires_symbolicator
  497. @pytest.mark.symbolicator
  498. def test_sourcemap_nofiles_source_expansion(self, process_with_symbolicator):
  499. project = self.project
  500. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  501. release.add_project(project)
  502. with open(get_fixture_path("nofiles.js"), "rb") as f:
  503. f_minified = File.objects.create(
  504. name="nofiles.js", type="release.file", headers={"Content-Type": "application/json"}
  505. )
  506. f_minified.putfile(f)
  507. ReleaseFile.objects.create(
  508. name=f"~/{f_minified.name}",
  509. release_id=release.id,
  510. organization_id=project.organization_id,
  511. file=f_minified,
  512. )
  513. with open(get_fixture_path("nofiles.js.map"), "rb") as f:
  514. f_sourcemap = File.objects.create(
  515. name="nofiles.js.map",
  516. type="release.file",
  517. headers={"Content-Type": "application/json"},
  518. )
  519. f_sourcemap.putfile(f)
  520. ReleaseFile.objects.create(
  521. name=f"app:///{f_sourcemap.name}",
  522. release_id=release.id,
  523. organization_id=project.organization_id,
  524. file=f_sourcemap,
  525. )
  526. data = {
  527. "timestamp": self.min_ago,
  528. "message": "hello",
  529. "platform": "javascript",
  530. "release": "abc",
  531. "exception": {
  532. "values": [
  533. {
  534. "type": "Error",
  535. "stacktrace": {
  536. "frames": [{"abs_path": "app:///nofiles.js", "lineno": 1, "colno": 39}]
  537. },
  538. }
  539. ]
  540. },
  541. }
  542. event = self.post_and_retrieve_event(data)
  543. assert "errors" not in event.data
  544. exception = event.interfaces["exception"]
  545. frame_list = exception.values[0].stacktrace.frames
  546. assert len(frame_list) == 1
  547. frame = frame_list[0]
  548. assert frame.abs_path == "app:///nofiles.js"
  549. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  550. assert frame.context_line == "\treturn a + b; // fôo"
  551. if process_with_symbolicator:
  552. assert frame.post_context == ["}"]
  553. else:
  554. assert frame.post_context == ["}", ""]
  555. @requires_symbolicator
  556. @pytest.mark.symbolicator
  557. def test_indexed_sourcemap_source_expansion(self, process_with_symbolicator):
  558. self.project.update_option("sentry:scrape_javascript", False)
  559. release = Release.objects.create(
  560. organization_id=self.project.organization_id, version="abc"
  561. )
  562. release.add_project(self.project)
  563. for file in ["indexed.min.js", "file1.js", "file2.js", "indexed.sourcemap.js"]:
  564. with open(get_fixture_path(file), "rb") as f:
  565. f1 = File.objects.create(
  566. name=file,
  567. type="release.file",
  568. headers={},
  569. )
  570. f1.putfile(f)
  571. ReleaseFile.objects.create(
  572. name=f"http://example.com/{f1.name}",
  573. release_id=release.id,
  574. organization_id=self.project.organization_id,
  575. file=f1,
  576. )
  577. data = {
  578. "timestamp": self.min_ago,
  579. "message": "hello",
  580. "platform": "javascript",
  581. "release": "abc",
  582. "exception": {
  583. "values": [
  584. {
  585. "type": "Error",
  586. "stacktrace": {
  587. "frames": [
  588. {
  589. "abs_path": "http://example.com/indexed.min.js",
  590. "filename": "indexed.min.js",
  591. "lineno": 1,
  592. "colno": 39,
  593. },
  594. {
  595. "abs_path": "http://example.com/indexed.min.js",
  596. "filename": "indexed.min.js",
  597. "lineno": 2,
  598. "colno": 44,
  599. },
  600. ]
  601. },
  602. }
  603. ]
  604. },
  605. }
  606. event = self.post_and_retrieve_event(data)
  607. assert "errors" not in event.data
  608. exception = event.interfaces["exception"]
  609. frame_list = exception.values[0].stacktrace.frames
  610. frame = frame_list[0]
  611. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  612. expected = "\treturn a + b; // fôo"
  613. assert frame.context_line == expected
  614. if process_with_symbolicator:
  615. assert frame.post_context == ["}"]
  616. else:
  617. assert frame.post_context == ["}", ""]
  618. raw_frame_list = exception.values[0].raw_stacktrace.frames
  619. raw_frame = raw_frame_list[0]
  620. assert not raw_frame.pre_context
  621. assert raw_frame.context_line == 'function add(a,b){"use strict";return a+b}'
  622. if process_with_symbolicator:
  623. assert raw_frame.post_context == [
  624. 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  625. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}",
  626. "//# sourceMappingURL=indexed.sourcemap.js",
  627. ]
  628. else:
  629. assert raw_frame.post_context == [
  630. 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  631. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}",
  632. "//# sourceMappingURL=indexed.sourcemap.js",
  633. "",
  634. ]
  635. assert raw_frame.lineno == 1
  636. frame = frame_list[1]
  637. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  638. assert frame.context_line == "\treturn a * b;"
  639. assert frame.post_context == [
  640. "}",
  641. "function divide(a, b) {",
  642. '\t"use strict";',
  643. "\ttry {",
  644. "\t\treturn multiply(add(a, b), a, b) / c;",
  645. ]
  646. raw_frame = raw_frame_list[1]
  647. assert raw_frame.pre_context == ['function add(a,b){"use strict";return a+b}']
  648. assert (
  649. raw_frame.context_line
  650. == 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  651. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}"
  652. )
  653. if process_with_symbolicator:
  654. assert raw_frame.post_context == ["//# sourceMappingURL=indexed.sourcemap.js"]
  655. else:
  656. assert raw_frame.post_context == ["//# sourceMappingURL=indexed.sourcemap.js", ""]
  657. assert raw_frame.lineno == 2
  658. @requires_symbolicator
  659. @pytest.mark.symbolicator
  660. def test_expansion_via_debug(self, process_with_symbolicator):
  661. project = self.project
  662. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  663. release.add_project(project)
  664. # file.min.js
  665. # ------------
  666. with open(get_fixture_path("file.min.js"), "rb") as f:
  667. f_minified = File.objects.create(
  668. name="file.min.js",
  669. type="release.file",
  670. headers={"Content-Type": "application/json"},
  671. )
  672. f_minified.putfile(f)
  673. # Intentionally omit hostname - use alternate artifact path lookup instead
  674. # /file1.js vs http://example.com/file1.js
  675. ReleaseFile.objects.create(
  676. name=f"~/{f_minified.name}?foo=bar",
  677. release_id=release.id,
  678. organization_id=project.organization_id,
  679. file=f_minified,
  680. )
  681. # file1.js
  682. # ---------
  683. with open(get_fixture_path("file1.js"), "rb") as f:
  684. f1 = File.objects.create(
  685. name="file1.js", type="release.file", headers={"Content-Type": "application/json"}
  686. )
  687. f1.putfile(f)
  688. ReleaseFile.objects.create(
  689. name=f"http://example.com/{f1.name}",
  690. release_id=release.id,
  691. organization_id=project.organization_id,
  692. file=f1,
  693. )
  694. # file2.js
  695. # ----------
  696. with open(get_fixture_path("file2.js"), "rb") as f:
  697. f2 = File.objects.create(
  698. name="file2.js", type="release.file", headers={"Content-Type": "application/json"}
  699. )
  700. f2.putfile(f)
  701. ReleaseFile.objects.create(
  702. name=f"http://example.com/{f2.name}",
  703. release_id=release.id,
  704. organization_id=project.organization_id,
  705. file=f2,
  706. )
  707. # To verify that the full url has priority over the relative url,
  708. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  709. # hostname that points to an empty file. If the processor chooses
  710. # this empty file over the correct file2.js, it will not locate
  711. # context for the 2nd frame.
  712. with open(get_fixture_path("empty.js"), "rb") as f:
  713. f2_empty = File.objects.create(
  714. name="empty.js", type="release.file", headers={"Content-Type": "application/json"}
  715. )
  716. f2_empty.putfile(f)
  717. ReleaseFile.objects.create(
  718. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  719. release_id=release.id,
  720. organization_id=project.organization_id,
  721. file=f2_empty,
  722. )
  723. # sourcemap
  724. # ----------
  725. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  726. f_sourcemap = File.objects.create(
  727. name="file.sourcemap.js",
  728. type="release.file",
  729. headers={"Content-Type": "application/json"},
  730. )
  731. f_sourcemap.putfile(f)
  732. ReleaseFile.objects.create(
  733. name=f"http://example.com/{f_sourcemap.name}",
  734. release_id=release.id,
  735. organization_id=project.organization_id,
  736. file=f_sourcemap,
  737. )
  738. data = {
  739. "timestamp": self.min_ago,
  740. "message": "hello",
  741. "platform": "javascript",
  742. "release": "abc",
  743. "exception": {
  744. "values": [
  745. {
  746. "type": "Error",
  747. "stacktrace": {
  748. "frames": [
  749. {
  750. "abs_path": "http://example.com/file.min.js?foo=bar",
  751. "filename": "file.min.js",
  752. "lineno": 1,
  753. "colno": 39,
  754. },
  755. {
  756. "abs_path": "http://example.com/file.min.js?foo=bar",
  757. "filename": "file.min.js",
  758. "lineno": 1,
  759. "colno": 79,
  760. },
  761. ]
  762. },
  763. }
  764. ]
  765. },
  766. }
  767. event = self.post_and_retrieve_event(data)
  768. assert "errors" not in event.data
  769. exception = event.interfaces["exception"]
  770. frame_list = exception.values[0].stacktrace.frames
  771. frame = frame_list[0]
  772. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  773. assert frame.context_line == "\treturn a + b; // fôo"
  774. if process_with_symbolicator:
  775. assert frame.post_context == ["}"]
  776. else:
  777. assert frame.post_context == ["}", ""]
  778. frame = frame_list[1]
  779. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  780. assert frame.context_line == "\treturn a * b;"
  781. assert frame.post_context == [
  782. "}",
  783. "function divide(a, b) {",
  784. '\t"use strict";',
  785. "\ttry {",
  786. "\t\treturn multiply(add(a, b), a, b) / c;",
  787. ]
  788. @requires_symbolicator
  789. @pytest.mark.symbolicator
  790. def test_expansion_via_distribution_release_artifacts(self, process_with_symbolicator):
  791. project = self.project
  792. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  793. release.add_project(project)
  794. dist = release.add_dist("foo")
  795. # file.min.js
  796. # ------------
  797. with open(get_fixture_path("file.min.js"), "rb") as f:
  798. f_minified = File.objects.create(
  799. name="file.min.js",
  800. type="release.file",
  801. headers={"Content-Type": "application/json"},
  802. )
  803. f_minified.putfile(f)
  804. # Intentionally omit hostname - use alternate artifact path lookup instead
  805. # /file1.js vs http://example.com/file1.js
  806. ReleaseFile.objects.create(
  807. name=f"~/{f_minified.name}?foo=bar",
  808. release_id=release.id,
  809. dist_id=dist.id,
  810. organization_id=project.organization_id,
  811. file=f_minified,
  812. )
  813. # file1.js
  814. # ---------
  815. with open(get_fixture_path("file1.js"), "rb") as f:
  816. f1 = File.objects.create(
  817. name="file1.js",
  818. type="release.file",
  819. headers={"Content-Type": "application/json"},
  820. )
  821. f1.putfile(f)
  822. ReleaseFile.objects.create(
  823. name=f"http://example.com/{f1.name}",
  824. release_id=release.id,
  825. dist_id=dist.id,
  826. organization_id=project.organization_id,
  827. file=f1,
  828. )
  829. # file2.js
  830. # ----------
  831. with open(get_fixture_path("file2.js"), "rb") as f:
  832. f2 = File.objects.create(
  833. name="file2.js",
  834. type="release.file",
  835. headers={"Content-Type": "application/json"},
  836. )
  837. f2.putfile(f)
  838. ReleaseFile.objects.create(
  839. name=f"http://example.com/{f2.name}",
  840. release_id=release.id,
  841. dist_id=dist.id,
  842. organization_id=project.organization_id,
  843. file=f2,
  844. )
  845. # To verify that the full url has priority over the relative url,
  846. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  847. # hostname that points to an empty file. If the processor chooses
  848. # this empty file over the correct file2.js, it will not locate
  849. # context for the 2nd frame.
  850. with open(get_fixture_path("empty.js"), "rb") as f:
  851. f2_empty = File.objects.create(
  852. name="empty.js",
  853. type="release.file",
  854. headers={"Content-Type": "application/json"},
  855. )
  856. f2_empty.putfile(f)
  857. ReleaseFile.objects.create(
  858. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  859. release_id=release.id,
  860. dist_id=dist.id,
  861. organization_id=project.organization_id,
  862. file=f2_empty,
  863. )
  864. # sourcemap
  865. # ----------
  866. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  867. f_sourcemap = File.objects.create(
  868. name="file.sourcemap.js",
  869. type="release.file",
  870. headers={"Content-Type": "application/json"},
  871. )
  872. f_sourcemap.putfile(f)
  873. ReleaseFile.objects.create(
  874. name=f"http://example.com/{f_sourcemap.name}",
  875. release_id=release.id,
  876. dist_id=dist.id,
  877. organization_id=project.organization_id,
  878. file=f_sourcemap,
  879. )
  880. data = {
  881. "timestamp": self.min_ago,
  882. "message": "hello",
  883. "platform": "javascript",
  884. "release": "abc",
  885. "dist": "foo",
  886. "exception": {
  887. "values": [
  888. {
  889. "type": "Error",
  890. "stacktrace": {
  891. "frames": [
  892. {
  893. "abs_path": "http://example.com/file.min.js?foo=bar",
  894. "filename": "file.min.js",
  895. "lineno": 1,
  896. "colno": 39,
  897. },
  898. {
  899. "abs_path": "http://example.com/file.min.js?foo=bar",
  900. "filename": "file.min.js",
  901. "lineno": 1,
  902. "colno": 79,
  903. },
  904. ]
  905. },
  906. }
  907. ]
  908. },
  909. }
  910. event = self.post_and_retrieve_event(data)
  911. assert "errors" not in event.data
  912. exception = event.interfaces["exception"]
  913. frame_list = exception.values[0].stacktrace.frames
  914. frame = frame_list[0]
  915. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  916. assert frame.context_line == "\treturn a + b; // fôo"
  917. if process_with_symbolicator:
  918. assert frame.post_context == ["}"]
  919. else:
  920. assert frame.post_context == ["}", ""]
  921. frame = frame_list[1]
  922. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  923. assert frame.context_line == "\treturn a * b;"
  924. assert frame.post_context == [
  925. "}",
  926. "function divide(a, b) {",
  927. '\t"use strict";',
  928. "\ttry {",
  929. "\t\treturn multiply(add(a, b), a, b) / c;",
  930. ]
  931. @responses.activate
  932. def test_sourcemap_expansion_with_missing_source(self):
  933. """
  934. Tests a successful sourcemap expansion that points to source files
  935. that are not found.
  936. """
  937. responses.add(
  938. responses.GET,
  939. "http://example.com/file.min.js",
  940. body=load_fixture("file.min.js"),
  941. content_type="application/javascript; charset=utf-8",
  942. )
  943. responses.add(
  944. responses.GET,
  945. "http://example.com/file.sourcemap.js",
  946. body=load_fixture("file.sourcemap.js"),
  947. content_type="application/json; charset=utf-8",
  948. )
  949. responses.add(responses.GET, "http://example.com/file1.js", body="Not Found", status=404)
  950. data = {
  951. "timestamp": self.min_ago,
  952. "message": "hello",
  953. "platform": "javascript",
  954. "exception": {
  955. "values": [
  956. {
  957. "type": "Error",
  958. "stacktrace": {
  959. # Add two frames. We only want to see the
  960. # error once though.
  961. "frames": [
  962. {
  963. "abs_path": "http://example.com/file.min.js",
  964. "filename": "file.min.js",
  965. "lineno": 1,
  966. "colno": 39,
  967. },
  968. {
  969. "abs_path": "http://example.com/file.min.js",
  970. "filename": "file.min.js",
  971. "lineno": 1,
  972. "colno": 39,
  973. },
  974. ]
  975. },
  976. }
  977. ]
  978. },
  979. }
  980. event = self.post_and_retrieve_event(data)
  981. assert event.data["errors"] == [
  982. {"url": "http://example.com/file1.js", "type": "fetch_invalid_http_code", "value": 404}
  983. ]
  984. exception = event.interfaces["exception"]
  985. frame_list = exception.values[0].stacktrace.frames
  986. frame = frame_list[0]
  987. # no context information ...
  988. assert not frame.pre_context
  989. assert not frame.context_line
  990. assert not frame.post_context
  991. # ... but line, column numbers are still correctly mapped
  992. assert frame.lineno == 3
  993. assert frame.colno == 9
  994. @responses.activate
  995. def test_failed_sourcemap_expansion(self):
  996. """
  997. Tests attempting to parse an indexed source map where each section has a "url"
  998. property - this is unsupported and should fail.
  999. """
  1000. responses.add(
  1001. responses.GET,
  1002. "http://example.com/unsupported.min.js",
  1003. body=load_fixture("unsupported.min.js"),
  1004. content_type="application/javascript; charset=utf-8",
  1005. )
  1006. responses.add(
  1007. responses.GET,
  1008. "http://example.com/unsupported.sourcemap.js",
  1009. body=load_fixture("unsupported.sourcemap.js"),
  1010. content_type="application/json; charset=utf-8",
  1011. )
  1012. data = {
  1013. "timestamp": self.min_ago,
  1014. "message": "hello",
  1015. "platform": "javascript",
  1016. "exception": {
  1017. "values": [
  1018. {
  1019. "type": "Error",
  1020. "stacktrace": {
  1021. "frames": [
  1022. {
  1023. "abs_path": "http://example.com/unsupported.min.js",
  1024. "filename": "indexed.min.js",
  1025. "lineno": 1,
  1026. "colno": 39,
  1027. }
  1028. ]
  1029. },
  1030. }
  1031. ]
  1032. },
  1033. }
  1034. event = self.post_and_retrieve_event(data)
  1035. assert event.data["errors"] == [
  1036. {"url": "http://example.com/unsupported.sourcemap.js", "type": "js_invalid_source"}
  1037. ]
  1038. def test_failed_sourcemap_expansion_data_url(self):
  1039. data = {
  1040. "timestamp": self.min_ago,
  1041. "message": "hello",
  1042. "platform": "javascript",
  1043. "exception": {
  1044. "values": [
  1045. {
  1046. "type": "Error",
  1047. "stacktrace": {
  1048. "frames": [
  1049. {
  1050. "abs_path": "data:application/javascript,base46,asfasf",
  1051. "filename": "indexed.min.js",
  1052. "lineno": 1,
  1053. "colno": 39,
  1054. }
  1055. ]
  1056. },
  1057. }
  1058. ]
  1059. },
  1060. }
  1061. event = self.post_and_retrieve_event(data)
  1062. assert event.data["errors"] == [{"url": "<data url>", "type": "js_no_source"}]
  1063. @responses.activate
  1064. def test_failed_sourcemap_expansion_missing_location_entirely(self):
  1065. responses.add(
  1066. responses.GET,
  1067. "http://example.com/indexed.min.js",
  1068. body="//# sourceMappingURL=indexed.sourcemap.js",
  1069. )
  1070. responses.add(responses.GET, "http://example.com/indexed.sourcemap.js", body="{}")
  1071. data = {
  1072. "timestamp": self.min_ago,
  1073. "message": "hello",
  1074. "platform": "javascript",
  1075. "exception": {
  1076. "values": [
  1077. {
  1078. "type": "Error",
  1079. "stacktrace": {
  1080. "frames": [
  1081. {
  1082. "abs_path": "http://example.com/indexed.min.js",
  1083. "filename": "indexed.min.js",
  1084. "lineno": 1,
  1085. "colno": 1,
  1086. },
  1087. {
  1088. "abs_path": "http://example.com/indexed.min.js",
  1089. "filename": "indexed.min.js",
  1090. },
  1091. ]
  1092. },
  1093. }
  1094. ]
  1095. },
  1096. }
  1097. event = self.post_and_retrieve_event(data)
  1098. assert "errors" not in event.data
  1099. @responses.activate
  1100. def test_html_response_for_js(self):
  1101. responses.add(
  1102. responses.GET,
  1103. "http://example.com/invalid_file1.js",
  1104. body=" <!DOCTYPE html><html><head></head><body></body></html>",
  1105. )
  1106. responses.add(
  1107. responses.GET,
  1108. "http://example.com/invalid_file2.js",
  1109. body="<!doctype html><html><head></head><body></body></html>",
  1110. )
  1111. responses.add(
  1112. responses.GET,
  1113. "http://example.com/valid_file.html",
  1114. body=(
  1115. "<!doctype html><html><head></head><body><script>/*legit case*/</script></body></html>"
  1116. ),
  1117. )
  1118. data = {
  1119. "timestamp": self.min_ago,
  1120. "message": "hello",
  1121. "platform": "javascript",
  1122. "exception": {
  1123. "values": [
  1124. {
  1125. "type": "Error",
  1126. "stacktrace": {
  1127. "frames": [
  1128. {
  1129. "abs_path": "http://example.com/invalid_file1.js",
  1130. "filename": "invalid_file1.js",
  1131. "lineno": 1,
  1132. "colno": 39,
  1133. },
  1134. {
  1135. "abs_path": "http://example.com/invalid_file2.js",
  1136. "filename": "invalid_file2.js",
  1137. "lineno": 1,
  1138. "colno": 39,
  1139. },
  1140. {
  1141. "abs_path": "http://example.com/valid_file.html",
  1142. "filename": "valid_file.html",
  1143. "lineno": 1,
  1144. "colno": 1,
  1145. },
  1146. ]
  1147. },
  1148. }
  1149. ]
  1150. },
  1151. }
  1152. event = self.post_and_retrieve_event(data)
  1153. assert event.data["errors"] == [
  1154. {"url": "http://example.com/invalid_file1.js", "type": "js_invalid_content"},
  1155. {"url": "http://example.com/invalid_file2.js", "type": "js_invalid_content"},
  1156. ]
  1157. def _test_expansion_via_release_archive(
  1158. self, link_sourcemaps: bool, process_with_symbolicator: bool
  1159. ):
  1160. project = self.project
  1161. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1162. release.add_project(project)
  1163. manifest = {
  1164. "org": self.organization.slug,
  1165. "release": release.version,
  1166. "files": {
  1167. "files/_/_/file.min.js": {
  1168. "url": "http://example.com/file.min.js",
  1169. "type": "minified_source",
  1170. },
  1171. "files/_/_/file1.js": {
  1172. "url": "http://example.com/file1.js",
  1173. "type": "source",
  1174. },
  1175. "files/_/_/file2.js": {
  1176. "url": "http://example.com/file2.js",
  1177. "type": "source",
  1178. },
  1179. "files/_/_/file.sourcemap.js": {
  1180. "url": "http://example.com/file.sourcemap.js",
  1181. "type": "source_map",
  1182. },
  1183. },
  1184. }
  1185. compressed = BytesIO(b"SYSB")
  1186. with zipfile.ZipFile(compressed, "a") as zip_file:
  1187. for rel_path, entry in manifest["files"].items():
  1188. name = os.path.basename(rel_path)
  1189. content = load_fixture(name)
  1190. if name == "file.min.js" and not link_sourcemaps:
  1191. # Remove link to source map, add to header instead
  1192. content = content.replace(b"//@ sourceMappingURL=file.sourcemap.js", b"")
  1193. entry["headers"] = {"Sourcemap": "file.sourcemap.js"}
  1194. zip_file.writestr(rel_path, content)
  1195. zip_file.writestr("manifest.json", json.dumps(manifest))
  1196. compressed.seek(0)
  1197. file = File.objects.create(name="doesnt_matter", type="release.bundle")
  1198. file.putfile(compressed)
  1199. update_artifact_index(release, None, file)
  1200. data = {
  1201. "timestamp": self.min_ago,
  1202. "message": "hello",
  1203. "platform": "javascript",
  1204. "release": "abc",
  1205. "exception": {
  1206. "values": [
  1207. {
  1208. "type": "Error",
  1209. "stacktrace": {
  1210. "frames": [
  1211. {
  1212. "abs_path": "http://example.com/file.min.js",
  1213. "filename": "file.min.js",
  1214. "lineno": 1,
  1215. "colno": 39,
  1216. },
  1217. {
  1218. "abs_path": "http://example.com/file.min.js",
  1219. "filename": "file.min.js",
  1220. "lineno": 1,
  1221. "colno": 79,
  1222. },
  1223. ]
  1224. },
  1225. }
  1226. ]
  1227. },
  1228. }
  1229. event = self.post_and_retrieve_event(data)
  1230. assert "errors" not in event.data
  1231. exception = event.interfaces["exception"]
  1232. frame_list = exception.values[0].stacktrace.frames
  1233. frame = frame_list[0]
  1234. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1235. assert frame.context_line == "\treturn a + b; // fôo"
  1236. if process_with_symbolicator:
  1237. assert frame.post_context == ["}"]
  1238. else:
  1239. assert frame.post_context == ["}", ""]
  1240. frame = frame_list[1]
  1241. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1242. assert frame.context_line == "\treturn a * b;"
  1243. assert frame.post_context == [
  1244. "}",
  1245. "function divide(a, b) {",
  1246. '\t"use strict";',
  1247. "\ttry {",
  1248. "\t\treturn multiply(add(a, b), a, b) / c;",
  1249. ]
  1250. @requires_symbolicator
  1251. @pytest.mark.symbolicator
  1252. def test_expansion_via_release_archive(self, process_with_symbolicator):
  1253. self._test_expansion_via_release_archive(
  1254. link_sourcemaps=True, process_with_symbolicator=process_with_symbolicator
  1255. )
  1256. @requires_symbolicator
  1257. @pytest.mark.symbolicator
  1258. def test_expansion_via_release_archive_no_sourcemap_link(self, process_with_symbolicator):
  1259. self._test_expansion_via_release_archive(
  1260. link_sourcemaps=False, process_with_symbolicator=process_with_symbolicator
  1261. )
  1262. @requires_symbolicator
  1263. @pytest.mark.symbolicator
  1264. def test_node_processing(self, process_with_symbolicator):
  1265. project = self.project
  1266. release = Release.objects.create(
  1267. organization_id=project.organization_id, version="nodeabc123"
  1268. )
  1269. release.add_project(project)
  1270. with open(get_fixture_path("dist.bundle.js"), "rb") as f:
  1271. f_minified = File.objects.create(
  1272. name="dist.bundle.js",
  1273. type="release.file",
  1274. headers={"Content-Type": "application/javascript"},
  1275. )
  1276. f_minified.putfile(f)
  1277. ReleaseFile.objects.create(
  1278. name=f"~/{f_minified.name}",
  1279. release_id=release.id,
  1280. organization_id=project.organization_id,
  1281. file=f_minified,
  1282. )
  1283. with open(get_fixture_path("dist.bundle.js.map"), "rb") as f:
  1284. f_sourcemap = File.objects.create(
  1285. name="dist.bundle.js.map",
  1286. type="release.file",
  1287. headers={"Content-Type": "application/javascript"},
  1288. )
  1289. f_sourcemap.putfile(f)
  1290. ReleaseFile.objects.create(
  1291. name=f"~/{f_sourcemap.name}",
  1292. release_id=release.id,
  1293. organization_id=project.organization_id,
  1294. file=f_sourcemap,
  1295. )
  1296. data = {
  1297. "timestamp": self.min_ago,
  1298. "message": "hello",
  1299. "platform": "node",
  1300. "release": "nodeabc123",
  1301. "exception": {
  1302. "values": [
  1303. {
  1304. "type": "Error",
  1305. "stacktrace": {
  1306. "frames": [
  1307. {
  1308. "filename": "app:///dist.bundle.js",
  1309. "function": "bar",
  1310. "lineno": 9,
  1311. "colno": 2321,
  1312. },
  1313. {
  1314. "filename": "app:///dist.bundle.js",
  1315. "function": "foo",
  1316. "lineno": 3,
  1317. "colno": 2308,
  1318. },
  1319. {
  1320. "filename": "app:///dist.bundle.js",
  1321. "function": "App",
  1322. "lineno": 3,
  1323. "colno": 1011,
  1324. },
  1325. {
  1326. "filename": "app:///dist.bundle.js",
  1327. "function": "Object.<anonymous>",
  1328. "lineno": 1,
  1329. "colno": 1014,
  1330. },
  1331. {
  1332. "filename": "app:///dist.bundle.js",
  1333. "function": "__webpack_require__",
  1334. "lineno": 20,
  1335. "colno": 30,
  1336. },
  1337. {
  1338. "filename": "app:///dist.bundle.js",
  1339. "function": "<unknown>",
  1340. "lineno": 18,
  1341. "colno": 63,
  1342. },
  1343. ]
  1344. },
  1345. }
  1346. ]
  1347. },
  1348. }
  1349. event = self.post_and_retrieve_event(data)
  1350. exception = event.interfaces["exception"]
  1351. frame_list = exception.values[0].stacktrace.frames
  1352. assert len(frame_list) == 6
  1353. assert frame_list[0].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1354. assert frame_list[0].function == "bar"
  1355. assert frame_list[0].lineno == 8
  1356. assert frame_list[1].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1357. assert frame_list[1].function == "foo"
  1358. assert frame_list[1].lineno == 2
  1359. assert frame_list[2].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1360. assert frame_list[2].function == "App"
  1361. assert frame_list[2].lineno == 2
  1362. assert frame_list[3].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1363. assert frame_list[3].function == "Object.<anonymous>"
  1364. assert frame_list[3].lineno == 1
  1365. assert frame_list[4].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1366. assert frame_list[4].function == "__webpack_require__"
  1367. assert frame_list[4].lineno == 19
  1368. assert frame_list[5].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1369. assert frame_list[5].function == "<unknown>"
  1370. assert frame_list[5].lineno == 16
  1371. @responses.activate
  1372. def test_no_fetch_from_http(self):
  1373. responses.add(
  1374. responses.GET,
  1375. "http://example.com/node_app.min.js",
  1376. body=load_fixture("node_app.min.js"),
  1377. content_type="application/javascript; charset=utf-8",
  1378. )
  1379. responses.add(
  1380. responses.GET,
  1381. "http://example.com/node_app.min.js.map",
  1382. body=load_fixture("node_app.min.js.map"),
  1383. content_type="application/javascript; charset=utf-8",
  1384. )
  1385. data = {
  1386. "timestamp": self.min_ago,
  1387. "message": "hello",
  1388. "platform": "node",
  1389. "exception": {
  1390. "values": [
  1391. {
  1392. "type": "Error",
  1393. "stacktrace": {
  1394. "frames": [
  1395. {
  1396. "abs_path": "node_bootstrap.js",
  1397. "filename": "node_bootstrap.js",
  1398. "lineno": 1,
  1399. "colno": 38,
  1400. },
  1401. {
  1402. "abs_path": "timers.js",
  1403. "filename": "timers.js",
  1404. "lineno": 1,
  1405. "colno": 39,
  1406. },
  1407. {
  1408. "abs_path": "webpack:///internal",
  1409. "filename": "internal",
  1410. "lineno": 1,
  1411. "colno": 43,
  1412. },
  1413. {
  1414. "abs_path": "webpack:///~/some_dep/file.js",
  1415. "filename": "file.js",
  1416. "lineno": 1,
  1417. "colno": 41,
  1418. },
  1419. {
  1420. "abs_path": "webpack:///./node_modules/file.js",
  1421. "filename": "file.js",
  1422. "lineno": 1,
  1423. "colno": 42,
  1424. },
  1425. {
  1426. "abs_path": "http://example.com/node_app.min.js",
  1427. "filename": "node_app.min.js",
  1428. "lineno": 1,
  1429. "colno": 40,
  1430. },
  1431. ]
  1432. },
  1433. }
  1434. ]
  1435. },
  1436. }
  1437. event = self.post_and_retrieve_event(data)
  1438. exception = event.interfaces["exception"]
  1439. frame_list = exception.values[0].stacktrace.frames
  1440. # This one should not process, so this one should be none.
  1441. assert exception.values[0].raw_stacktrace is None
  1442. # None of the in app should update
  1443. for x in range(6):
  1444. assert not frame_list[x].in_app
  1445. @responses.activate
  1446. def test_html_file_with_query_param_ending_with_js_extension(self):
  1447. responses.add(
  1448. responses.GET,
  1449. "http://example.com/file.html",
  1450. body=(
  1451. "<!doctype html><html><head></head><body><script>/*legit case*/</script></body></html>"
  1452. ),
  1453. )
  1454. data = {
  1455. "timestamp": self.min_ago,
  1456. "message": "hello",
  1457. "platform": "javascript",
  1458. "exception": {
  1459. "values": [
  1460. {
  1461. "type": "Error",
  1462. "stacktrace": {
  1463. "frames": [
  1464. {
  1465. "abs_path": "http://example.com/file.html?sw=iddqd1337.js",
  1466. "filename": "file.html",
  1467. "lineno": 1,
  1468. "colno": 1,
  1469. },
  1470. ]
  1471. },
  1472. }
  1473. ]
  1474. },
  1475. }
  1476. event = self.post_and_retrieve_event(data)
  1477. assert "errors" not in event.data
  1478. def test_expansion_with_allow_scraping_false(self):
  1479. project = self.project
  1480. project.organization.update_option("sentry:scrape_javascript", False)
  1481. data = {
  1482. "timestamp": self.min_ago,
  1483. "message": "hello",
  1484. "platform": "javascript",
  1485. "release": "1.0",
  1486. "exception": {
  1487. "values": [
  1488. {
  1489. "type": "Error",
  1490. "stacktrace": {
  1491. "frames": [
  1492. {
  1493. "abs_path": "http://example.com/file.min.js",
  1494. "filename": "file.min.js",
  1495. "lineno": 1,
  1496. "colno": 39,
  1497. },
  1498. ]
  1499. },
  1500. }
  1501. ]
  1502. },
  1503. }
  1504. event = self.post_and_retrieve_event(data)
  1505. assert len(event.data["errors"]) == 1
  1506. assert event.data["errors"][0] == {
  1507. "type": "js_scraping_disabled",
  1508. "url": "http://example.com/file.min.js",
  1509. }
  1510. @requires_symbolicator
  1511. @pytest.mark.symbolicator
  1512. def test_expansion_with_debug_id(self, process_with_symbolicator):
  1513. project = self.project
  1514. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1515. release.add_project(project)
  1516. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1517. compressed = BytesIO(b"SYSB")
  1518. with zipfile.ZipFile(compressed, "a") as zip_file:
  1519. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1520. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1521. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1522. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1523. zip_file.writestr(
  1524. "files/_/_/file.wc.sourcemap.js", load_fixture("file.wc.sourcemap.js")
  1525. )
  1526. zip_file.writestr(
  1527. "manifest.json",
  1528. json.dumps(
  1529. {
  1530. "org": self.organization.slug,
  1531. "release": release.version,
  1532. "files": {
  1533. "files/_/_/file.min.js": {
  1534. "url": "~/file.min.js",
  1535. "type": "minified_source",
  1536. "headers": {
  1537. "content-type": "application/json",
  1538. "debug-id": debug_id,
  1539. "sourcemap": "file.sourcemap.js",
  1540. },
  1541. },
  1542. "files/_/_/file1.js": {
  1543. "url": "~/file1.js",
  1544. "type": "source",
  1545. "headers": {
  1546. "content-type": "application/json",
  1547. },
  1548. },
  1549. "files/_/_/file2.js": {
  1550. "url": "~/file2.js",
  1551. "type": "source",
  1552. "headers": {
  1553. "content-type": "application/json",
  1554. },
  1555. },
  1556. "files/_/_/empty.js": {
  1557. "url": "~/empty.js",
  1558. "type": "source",
  1559. "headers": {
  1560. "content-type": "application/json",
  1561. },
  1562. },
  1563. "files/_/_/file.wc.sourcemap.js": {
  1564. "url": "~/file.wc.sourcemap.js",
  1565. "type": "source_map",
  1566. "headers": {
  1567. "content-type": "application/json",
  1568. "debug-id": debug_id,
  1569. },
  1570. },
  1571. },
  1572. }
  1573. ),
  1574. )
  1575. compressed.seek(0)
  1576. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1577. file.putfile(compressed)
  1578. # We want to also store the release files for this bundle, to check if they work together.
  1579. compressed.seek(0)
  1580. file_for_release = File.objects.create(name="bundle.zip", type="release.bundle")
  1581. file_for_release.putfile(compressed)
  1582. update_artifact_index(release, None, file_for_release)
  1583. artifact_bundle = ArtifactBundle.objects.create(
  1584. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1585. )
  1586. ProjectArtifactBundle.objects.create(
  1587. organization_id=self.organization.id,
  1588. project_id=self.project.id,
  1589. artifact_bundle=artifact_bundle,
  1590. )
  1591. DebugIdArtifactBundle.objects.create(
  1592. organization_id=self.organization.id,
  1593. debug_id=debug_id,
  1594. artifact_bundle=artifact_bundle,
  1595. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1596. )
  1597. DebugIdArtifactBundle.objects.create(
  1598. organization_id=self.organization.id,
  1599. debug_id=debug_id,
  1600. artifact_bundle=artifact_bundle,
  1601. source_file_type=SourceFileType.SOURCE_MAP.value,
  1602. )
  1603. data = {
  1604. "timestamp": self.min_ago,
  1605. "message": "hello",
  1606. "platform": "javascript",
  1607. "release": "abc",
  1608. "exception": {
  1609. "values": [
  1610. {
  1611. "type": "Error",
  1612. "stacktrace": {
  1613. "frames": [
  1614. {
  1615. "abs_path": "http://example.com/file.min.js",
  1616. "filename": "file.min.js",
  1617. "lineno": 1,
  1618. "colno": 39,
  1619. },
  1620. {
  1621. "abs_path": "http://example.com/file.min.js",
  1622. "filename": "file.min.js",
  1623. "lineno": 1,
  1624. "colno": 79,
  1625. },
  1626. # We want also to test the source without minification.
  1627. {
  1628. "abs_path": "http://example.com/file1.js",
  1629. "filename": "file1.js",
  1630. "lineno": 3,
  1631. "colno": 12,
  1632. },
  1633. ]
  1634. },
  1635. }
  1636. ]
  1637. },
  1638. "debug_meta": {
  1639. "images": [
  1640. {
  1641. "type": "sourcemap",
  1642. "debug_id": debug_id,
  1643. "code_file": "http://example.com/file.min.js",
  1644. }
  1645. ]
  1646. },
  1647. }
  1648. event = self.post_and_retrieve_event(data)
  1649. assert "errors" not in event.data
  1650. exception = event.interfaces["exception"]
  1651. frame_list = exception.values[0].stacktrace.frames
  1652. frame = frame_list[0]
  1653. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1654. assert frame.context_line == "\treturn a + b; // fôo"
  1655. if process_with_symbolicator:
  1656. assert frame.post_context == ["}"]
  1657. else:
  1658. assert frame.post_context == ["}", ""]
  1659. frame = frame_list[1]
  1660. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1661. assert frame.context_line == "\treturn a * b;"
  1662. assert frame.post_context == [
  1663. "}",
  1664. "function divide(a, b) {",
  1665. '\t"use strict";',
  1666. "\ttry {",
  1667. "\t\treturn multiply(add(a, b), a, b) / c;",
  1668. ]
  1669. frame = frame_list[2]
  1670. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1671. assert frame.context_line == "\treturn a + b; // fôo"
  1672. if process_with_symbolicator:
  1673. assert frame.post_context == ["}"]
  1674. else:
  1675. assert frame.post_context == ["}", ""]
  1676. @requires_symbolicator
  1677. @pytest.mark.symbolicator
  1678. def test_expansion_with_debug_id_and_sourcemap_without_sources_content(
  1679. self, process_with_symbolicator
  1680. ):
  1681. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1682. compressed = BytesIO(b"SYSB")
  1683. with zipfile.ZipFile(compressed, "a") as zip_file:
  1684. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1685. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1686. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1687. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1688. zip_file.writestr("files/_/_/file.sourcemap.js", load_fixture("file.sourcemap.js"))
  1689. zip_file.writestr(
  1690. "manifest.json",
  1691. json.dumps(
  1692. {
  1693. "files": {
  1694. "files/_/_/file.min.js": {
  1695. "url": "~/file.min.js",
  1696. "type": "minified_source",
  1697. "headers": {
  1698. "content-type": "application/json",
  1699. "debug-id": debug_id,
  1700. "sourcemap": "file.sourcemap.js",
  1701. },
  1702. },
  1703. "files/_/_/file1.js": {
  1704. "url": "~/file1.js",
  1705. "type": "source",
  1706. "headers": {
  1707. "content-type": "application/json",
  1708. },
  1709. },
  1710. "files/_/_/file2.js": {
  1711. "url": "~/file2.js",
  1712. "type": "source",
  1713. "headers": {
  1714. "content-type": "application/json",
  1715. },
  1716. },
  1717. "files/_/_/empty.js": {
  1718. "url": "~/empty.js",
  1719. "type": "source",
  1720. "headers": {
  1721. "content-type": "application/json",
  1722. },
  1723. },
  1724. "files/_/_/file.sourcemap.js": {
  1725. "url": "~/file.sourcemap.js",
  1726. "type": "source_map",
  1727. "headers": {
  1728. "content-type": "application/json",
  1729. "debug-id": debug_id,
  1730. },
  1731. },
  1732. }
  1733. }
  1734. ),
  1735. )
  1736. compressed.seek(0)
  1737. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1738. file.putfile(compressed)
  1739. artifact_bundle = ArtifactBundle.objects.create(
  1740. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1741. )
  1742. ProjectArtifactBundle.objects.create(
  1743. organization_id=self.organization.id,
  1744. project_id=self.project.id,
  1745. artifact_bundle=artifact_bundle,
  1746. )
  1747. DebugIdArtifactBundle.objects.create(
  1748. organization_id=self.organization.id,
  1749. debug_id=debug_id,
  1750. artifact_bundle=artifact_bundle,
  1751. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1752. )
  1753. DebugIdArtifactBundle.objects.create(
  1754. organization_id=self.organization.id,
  1755. debug_id=debug_id,
  1756. artifact_bundle=artifact_bundle,
  1757. source_file_type=SourceFileType.SOURCE_MAP.value,
  1758. )
  1759. data = {
  1760. "timestamp": self.min_ago,
  1761. "message": "hello",
  1762. "platform": "javascript",
  1763. "release": "abc",
  1764. "exception": {
  1765. "values": [
  1766. {
  1767. "type": "Error",
  1768. "stacktrace": {
  1769. "frames": [
  1770. {
  1771. "abs_path": "http://example.com/file.min.js",
  1772. "filename": "file.min.js",
  1773. "lineno": 1,
  1774. "colno": 39,
  1775. },
  1776. {
  1777. "abs_path": "http://example.com/file.min.js",
  1778. "filename": "file.min.js",
  1779. "lineno": 1,
  1780. "colno": 79,
  1781. },
  1782. ]
  1783. },
  1784. }
  1785. ]
  1786. },
  1787. "debug_meta": {
  1788. "images": [
  1789. {
  1790. "type": "sourcemap",
  1791. "debug_id": debug_id,
  1792. "code_file": "http://example.com/file.min.js",
  1793. }
  1794. ]
  1795. },
  1796. }
  1797. event = self.post_and_retrieve_event(data)
  1798. # NOTE: Symbolicator processor has a better fallback to pull specific files for source context
  1799. if process_with_symbolicator:
  1800. assert "errors" not in event.data
  1801. exception = event.interfaces["exception"]
  1802. frame_list = exception.values[0].stacktrace.frames
  1803. frame = frame_list[0]
  1804. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1805. assert frame.context_line == "\treturn a + b; // fôo"
  1806. if process_with_symbolicator:
  1807. assert frame.post_context == ["}"]
  1808. else:
  1809. assert frame.post_context == ["}", ""]
  1810. frame = frame_list[1]
  1811. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1812. assert frame.context_line == "\treturn a * b;"
  1813. assert frame.post_context == [
  1814. "}",
  1815. "function divide(a, b) {",
  1816. '\t"use strict";',
  1817. "\ttry {",
  1818. "\t\treturn multiply(add(a, b), a, b) / c;",
  1819. ]
  1820. else:
  1821. assert len(event.data["errors"]) == 3
  1822. assert event.data["errors"][0] == {
  1823. "type": "js_missing_sources_content",
  1824. "source": "http://example.com/file.min.js",
  1825. "sourcemap": f"debug-id://{debug_id}/~/file.sourcemap.js",
  1826. }
  1827. @requires_symbolicator
  1828. @pytest.mark.symbolicator
  1829. def test_expansion_with_debug_id_and_malformed_sourcemap(self, process_with_symbolicator):
  1830. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1831. compressed = BytesIO(b"SYSB")
  1832. with zipfile.ZipFile(compressed, "a") as zip_file:
  1833. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1834. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1835. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1836. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1837. zip_file.writestr(
  1838. "files/_/_/file.malformed.sourcemap.js", load_fixture("file.malformed.sourcemap.js")
  1839. )
  1840. zip_file.writestr(
  1841. "manifest.json",
  1842. json.dumps(
  1843. {
  1844. "files": {
  1845. "files/_/_/file.min.js": {
  1846. "url": "~/file.min.js",
  1847. "type": "minified_source",
  1848. "headers": {
  1849. "content-type": "application/json",
  1850. "debug-id": debug_id,
  1851. "sourcemap": "file.malformed.sourcemap.js",
  1852. },
  1853. },
  1854. "files/_/_/file1.js": {
  1855. "url": "~/file1.js",
  1856. "type": "source",
  1857. "headers": {
  1858. "content-type": "application/json",
  1859. },
  1860. },
  1861. "files/_/_/file2.js": {
  1862. "url": "~/file2.js",
  1863. "type": "source",
  1864. "headers": {
  1865. "content-type": "application/json",
  1866. },
  1867. },
  1868. "files/_/_/empty.js": {
  1869. "url": "~/empty.js",
  1870. "type": "source",
  1871. "headers": {
  1872. "content-type": "application/json",
  1873. },
  1874. },
  1875. "files/_/_/file.malformed.sourcemap.js": {
  1876. "url": "~/file.malformed.sourcemap.js",
  1877. "type": "source_map",
  1878. "headers": {
  1879. "content-type": "application/json",
  1880. "debug-id": debug_id,
  1881. },
  1882. },
  1883. }
  1884. }
  1885. ),
  1886. )
  1887. compressed.seek(0)
  1888. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1889. file.putfile(compressed)
  1890. artifact_bundle = ArtifactBundle.objects.create(
  1891. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1892. )
  1893. ProjectArtifactBundle.objects.create(
  1894. organization_id=self.organization.id,
  1895. project_id=self.project.id,
  1896. artifact_bundle=artifact_bundle,
  1897. )
  1898. DebugIdArtifactBundle.objects.create(
  1899. organization_id=self.organization.id,
  1900. debug_id=debug_id,
  1901. artifact_bundle=artifact_bundle,
  1902. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1903. )
  1904. DebugIdArtifactBundle.objects.create(
  1905. organization_id=self.organization.id,
  1906. debug_id=debug_id,
  1907. artifact_bundle=artifact_bundle,
  1908. source_file_type=SourceFileType.SOURCE_MAP.value,
  1909. )
  1910. data = {
  1911. "timestamp": self.min_ago,
  1912. "message": "hello",
  1913. "platform": "javascript",
  1914. "release": "abc",
  1915. "exception": {
  1916. "values": [
  1917. {
  1918. "type": "Error",
  1919. "stacktrace": {
  1920. "frames": [
  1921. {
  1922. "abs_path": "http://example.com/file.min.js",
  1923. "filename": "file.min.js",
  1924. "lineno": 1,
  1925. "colno": 39,
  1926. },
  1927. {
  1928. "abs_path": "http://example.com/file.min.js",
  1929. "filename": "file.min.js",
  1930. "lineno": 1,
  1931. "colno": 79,
  1932. },
  1933. ]
  1934. },
  1935. }
  1936. ]
  1937. },
  1938. "debug_meta": {
  1939. "images": [
  1940. {
  1941. "type": "sourcemap",
  1942. "debug_id": debug_id,
  1943. "code_file": "http://example.com/file.min.js",
  1944. }
  1945. ]
  1946. },
  1947. }
  1948. event = self.post_and_retrieve_event(data)
  1949. assert len(event.data["errors"]) == 1
  1950. # NOTE: Its not obvious what data we want here yet.
  1951. if process_with_symbolicator:
  1952. assert event.data["errors"][0] == {
  1953. "type": "js_invalid_source",
  1954. "url": "http://example.com/file.malformed.sourcemap.js",
  1955. }
  1956. else:
  1957. assert event.data["errors"][0] == {
  1958. "type": "js_invalid_source",
  1959. "debug_id": f"debug-id://{debug_id}/~/file.malformed.sourcemap.js",
  1960. }
  1961. @requires_symbolicator
  1962. @pytest.mark.symbolicator
  1963. def test_expansion_with_debug_id_not_found(self, process_with_symbolicator):
  1964. project = self.project
  1965. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1966. release.add_project(project)
  1967. manifest = {
  1968. "org": self.organization.slug,
  1969. "release": release.version,
  1970. "files": {
  1971. "files/_/_/file.min.js": {
  1972. "url": "http://example.com/file.min.js",
  1973. "type": "minified_source",
  1974. },
  1975. "files/_/_/file1.js": {
  1976. "url": "http://example.com/file1.js",
  1977. "type": "source",
  1978. },
  1979. "files/_/_/file2.js": {
  1980. "url": "http://example.com/file2.js",
  1981. "type": "source",
  1982. },
  1983. "files/_/_/file.sourcemap.js": {
  1984. "url": "http://example.com/file.sourcemap.js",
  1985. "type": "source_map",
  1986. },
  1987. },
  1988. }
  1989. compressed = BytesIO(b"SYSB")
  1990. with zipfile.ZipFile(compressed, "a") as zip_file:
  1991. for rel_path, entry in manifest["files"].items():
  1992. name = os.path.basename(rel_path)
  1993. content = load_fixture(name)
  1994. if name == "file.min.js":
  1995. # Remove link to source map, add to header instead
  1996. content = content.replace(b"//@ sourceMappingURL=file.sourcemap.js", b"")
  1997. entry["headers"] = {"Sourcemap": "file.sourcemap.js"}
  1998. zip_file.writestr(rel_path, content)
  1999. zip_file.writestr("manifest.json", json.dumps(manifest))
  2000. compressed.seek(0)
  2001. file = File.objects.create(name="release_bundle.zip", type="release.bundle")
  2002. file.putfile(compressed)
  2003. update_artifact_index(release, None, file)
  2004. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  2005. data = {
  2006. "timestamp": self.min_ago,
  2007. "message": "hello",
  2008. "platform": "javascript",
  2009. "release": "abc",
  2010. "exception": {
  2011. "values": [
  2012. {
  2013. "type": "Error",
  2014. "stacktrace": {
  2015. "frames": [
  2016. {
  2017. "abs_path": "http://example.com/file.min.js",
  2018. "filename": "file.min.js",
  2019. "lineno": 1,
  2020. "colno": 39,
  2021. },
  2022. {
  2023. "abs_path": "http://example.com/file.min.js",
  2024. "filename": "file.min.js",
  2025. "lineno": 1,
  2026. "colno": 79,
  2027. },
  2028. # We want also to test the source without minification.
  2029. {
  2030. "abs_path": "http://example.com/file1.js",
  2031. "filename": "file1.js",
  2032. "lineno": 3,
  2033. "colno": 12,
  2034. },
  2035. ]
  2036. },
  2037. }
  2038. ]
  2039. },
  2040. "debug_meta": {
  2041. "images": [
  2042. {
  2043. "type": "sourcemap",
  2044. "debug_id": debug_id,
  2045. "code_file": "http://example.com/file.min.js",
  2046. }
  2047. ]
  2048. },
  2049. }
  2050. event = self.post_and_retrieve_event(data)
  2051. exception = event.interfaces["exception"]
  2052. frame_list = exception.values[0].stacktrace.frames
  2053. frame = frame_list[0]
  2054. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2055. assert frame.context_line == "\treturn a + b; // fôo"
  2056. if process_with_symbolicator:
  2057. assert frame.post_context == ["}"]
  2058. else:
  2059. assert frame.post_context == ["}", ""]
  2060. frame = frame_list[1]
  2061. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  2062. assert frame.context_line == "\treturn a * b;"
  2063. assert frame.post_context == [
  2064. "}",
  2065. "function divide(a, b) {",
  2066. '\t"use strict";',
  2067. "\ttry {",
  2068. "\t\treturn multiply(add(a, b), a, b) / c;",
  2069. ]
  2070. frame = frame_list[2]
  2071. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2072. assert frame.context_line == "\treturn a + b; // fôo"
  2073. if process_with_symbolicator:
  2074. assert frame.post_context == ["}"]
  2075. else:
  2076. assert frame.post_context == ["}", ""]
  2077. @requires_symbolicator
  2078. @pytest.mark.symbolicator
  2079. def test_expansion_with_release_dist_pair_x(self, process_with_symbolicator):
  2080. project = self.project
  2081. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  2082. release.add_project(project)
  2083. dist = release.add_dist("android")
  2084. # We want to also add debug_id information inside the manifest but not in the stack trace to replicate a
  2085. # real edge case that we can incur in.
  2086. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  2087. compressed = BytesIO(b"SYSB")
  2088. with zipfile.ZipFile(compressed, "a") as zip_file:
  2089. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  2090. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  2091. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  2092. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  2093. zip_file.writestr(
  2094. "files/_/_/file.wc.sourcemap.js", load_fixture("file.wc.sourcemap.js")
  2095. )
  2096. zip_file.writestr(
  2097. "manifest.json",
  2098. json.dumps(
  2099. {
  2100. "files": {
  2101. "files/_/_/file.min.js": {
  2102. "url": "~/file.min.js",
  2103. "type": "minified_source",
  2104. "headers": {
  2105. "content-type": "application/json",
  2106. "sourcemap": "file.wc.sourcemap.js",
  2107. "debug-id": debug_id,
  2108. },
  2109. },
  2110. "files/_/_/file1.js": {
  2111. "url": "~/file1.js",
  2112. "type": "source",
  2113. "headers": {
  2114. "content-type": "application/json",
  2115. },
  2116. },
  2117. "files/_/_/file2.js": {
  2118. "url": "~/file2.js",
  2119. "type": "source",
  2120. "headers": {
  2121. "content-type": "application/json",
  2122. },
  2123. },
  2124. "files/_/_/empty.js": {
  2125. "url": "~/empty.js",
  2126. "type": "source",
  2127. "headers": {
  2128. "content-type": "application/json",
  2129. },
  2130. },
  2131. "files/_/_/file.wc.sourcemap.js": {
  2132. "url": "~/file.wc.sourcemap.js",
  2133. "type": "source_map",
  2134. "headers": {
  2135. "content-type": "application/json",
  2136. "debug-id": debug_id,
  2137. },
  2138. },
  2139. },
  2140. }
  2141. ),
  2142. )
  2143. compressed.seek(0)
  2144. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  2145. file.putfile(compressed)
  2146. artifact_bundle = ArtifactBundle.objects.create(
  2147. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  2148. )
  2149. ProjectArtifactBundle.objects.create(
  2150. organization_id=self.organization.id,
  2151. project_id=self.project.id,
  2152. artifact_bundle=artifact_bundle,
  2153. )
  2154. ReleaseArtifactBundle.objects.create(
  2155. organization_id=self.organization.id,
  2156. release_name=release.version,
  2157. dist_name=dist.name,
  2158. artifact_bundle=artifact_bundle,
  2159. )
  2160. data = {
  2161. "timestamp": self.min_ago,
  2162. "message": "hello",
  2163. "platform": "javascript",
  2164. "release": release.version,
  2165. "dist": dist.name,
  2166. "exception": {
  2167. "values": [
  2168. {
  2169. "type": "Error",
  2170. "stacktrace": {
  2171. "frames": [
  2172. {
  2173. "abs_path": "http://example.com/file.min.js",
  2174. "filename": "file.min.js",
  2175. "lineno": 1,
  2176. "colno": 39,
  2177. },
  2178. {
  2179. "abs_path": "http://example.com/file.min.js",
  2180. "filename": "file.min.js",
  2181. "lineno": 1,
  2182. "colno": 79,
  2183. },
  2184. # We want also to test the source without minification.
  2185. {
  2186. "abs_path": "http://example.com/file1.js",
  2187. "filename": "file1.js",
  2188. "lineno": 3,
  2189. "colno": 12,
  2190. },
  2191. ]
  2192. },
  2193. }
  2194. ]
  2195. },
  2196. }
  2197. event = self.post_and_retrieve_event(data)
  2198. assert "errors" not in event.data
  2199. exception = event.interfaces["exception"]
  2200. frame_list = exception.values[0].stacktrace.frames
  2201. frame = frame_list[0]
  2202. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2203. assert frame.context_line == "\treturn a + b; // fôo"
  2204. if process_with_symbolicator:
  2205. assert frame.post_context == ["}"]
  2206. else:
  2207. assert frame.post_context == ["}", ""]
  2208. frame = frame_list[1]
  2209. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  2210. assert frame.context_line == "\treturn a * b;"
  2211. assert frame.post_context == [
  2212. "}",
  2213. "function divide(a, b) {",
  2214. '\t"use strict";',
  2215. "\ttry {",
  2216. "\t\treturn multiply(add(a, b), a, b) / c;",
  2217. ]
  2218. frame = frame_list[2]
  2219. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2220. assert frame.context_line == "\treturn a + b; // fôo"
  2221. if process_with_symbolicator:
  2222. assert frame.post_context == ["}"]
  2223. else:
  2224. assert frame.post_context == ["}", ""]
  2225. @requires_symbolicator
  2226. @pytest.mark.symbolicator
  2227. def test_expansion_with_release_dist_pair_and_sourcemap_without_sources_content(
  2228. self, process_with_symbolicator
  2229. ):
  2230. project = self.project
  2231. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  2232. release.add_project(project)
  2233. dist = release.add_dist("android")
  2234. compressed = BytesIO(b"SYSB")
  2235. with zipfile.ZipFile(compressed, "a") as zip_file:
  2236. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  2237. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  2238. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  2239. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  2240. zip_file.writestr("files/_/_/file.sourcemap.js", load_fixture("file.sourcemap.js"))
  2241. zip_file.writestr(
  2242. "manifest.json",
  2243. json.dumps(
  2244. {
  2245. "files": {
  2246. "files/_/_/file.min.js": {
  2247. "url": "~/file.min.js",
  2248. "type": "minified_source",
  2249. "headers": {
  2250. "content-type": "application/json",
  2251. "sourcemap": "file.sourcemap.js",
  2252. },
  2253. },
  2254. "files/_/_/file1.js": {
  2255. "url": "~/file1.js",
  2256. "type": "source",
  2257. "headers": {
  2258. "content-type": "application/json",
  2259. },
  2260. },
  2261. "files/_/_/file2.js": {
  2262. "url": "~/file2.js",
  2263. "type": "source",
  2264. "headers": {
  2265. "content-type": "application/json",
  2266. },
  2267. },
  2268. "files/_/_/empty.js": {
  2269. "url": "~/empty.js",
  2270. "type": "source",
  2271. "headers": {
  2272. "content-type": "application/json",
  2273. },
  2274. },
  2275. "files/_/_/file.sourcemap.js": {
  2276. "url": "~/file.sourcemap.js",
  2277. "type": "source_map",
  2278. "headers": {
  2279. "content-type": "application/json",
  2280. },
  2281. },
  2282. }
  2283. }
  2284. ),
  2285. )
  2286. compressed.seek(0)
  2287. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  2288. file.putfile(compressed)
  2289. artifact_bundle = ArtifactBundle.objects.create(
  2290. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  2291. )
  2292. ProjectArtifactBundle.objects.create(
  2293. organization_id=self.organization.id,
  2294. project_id=self.project.id,
  2295. artifact_bundle=artifact_bundle,
  2296. )
  2297. ReleaseArtifactBundle.objects.create(
  2298. organization_id=self.organization.id,
  2299. release_name=release.version,
  2300. dist_name=dist.name,
  2301. artifact_bundle=artifact_bundle,
  2302. )
  2303. data = {
  2304. "timestamp": self.min_ago,
  2305. "message": "hello",
  2306. "platform": "javascript",
  2307. "release": release.version,
  2308. "dist": dist.name,
  2309. "exception": {
  2310. "values": [
  2311. {
  2312. "type": "Error",
  2313. "stacktrace": {
  2314. "frames": [
  2315. {
  2316. "abs_path": "http://example.com/file.min.js",
  2317. "filename": "file.min.js",
  2318. "lineno": 1,
  2319. "colno": 39,
  2320. },
  2321. {
  2322. "abs_path": "http://example.com/file.min.js",
  2323. "filename": "file.min.js",
  2324. "lineno": 1,
  2325. "colno": 79,
  2326. },
  2327. ]
  2328. },
  2329. }
  2330. ]
  2331. },
  2332. }
  2333. event = self.post_and_retrieve_event(data)
  2334. # NOTE: Symbolicator processor has a better fallback to pull specific files for source context
  2335. if process_with_symbolicator:
  2336. assert "errors" not in event.data
  2337. exception = event.interfaces["exception"]
  2338. frame_list = exception.values[0].stacktrace.frames
  2339. frame = frame_list[0]
  2340. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2341. assert frame.context_line == "\treturn a + b; // fôo"
  2342. if process_with_symbolicator:
  2343. assert frame.post_context == ["}"]
  2344. else:
  2345. assert frame.post_context == ["}", ""]
  2346. frame = frame_list[1]
  2347. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  2348. assert frame.context_line == "\treturn a * b;"
  2349. assert frame.post_context == [
  2350. "}",
  2351. "function divide(a, b) {",
  2352. '\t"use strict";',
  2353. "\ttry {",
  2354. "\t\treturn multiply(add(a, b), a, b) / c;",
  2355. ]
  2356. else:
  2357. assert len(event.data["errors"]) == 3
  2358. assert event.data["errors"][0] == {
  2359. "type": "js_missing_sources_content",
  2360. "source": "http://example.com/file.min.js",
  2361. "sourcemap": "http://example.com/file.sourcemap.js",
  2362. }
  2363. @requires_symbolicator
  2364. @pytest.mark.symbolicator
  2365. def test_expansion_with_release_and_malformed_sourcemap(self, process_with_symbolicator):
  2366. project = self.project
  2367. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  2368. release.add_project(project)
  2369. compressed = BytesIO(b"SYSB")
  2370. with zipfile.ZipFile(compressed, "a") as zip_file:
  2371. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  2372. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  2373. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  2374. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  2375. zip_file.writestr(
  2376. "files/_/_/file.malformed.sourcemap.js", load_fixture("file.malformed.sourcemap.js")
  2377. )
  2378. zip_file.writestr(
  2379. "manifest.json",
  2380. json.dumps(
  2381. {
  2382. "files": {
  2383. "files/_/_/file.min.js": {
  2384. "url": "~/file.min.js",
  2385. "type": "minified_source",
  2386. "headers": {
  2387. "content-type": "application/json",
  2388. "sourcemap": "file.malformed.sourcemap.js",
  2389. },
  2390. },
  2391. "files/_/_/file1.js": {
  2392. "url": "~/file1.js",
  2393. "type": "source",
  2394. "headers": {
  2395. "content-type": "application/json",
  2396. },
  2397. },
  2398. "files/_/_/file2.js": {
  2399. "url": "~/file2.js",
  2400. "type": "source",
  2401. "headers": {
  2402. "content-type": "application/json",
  2403. },
  2404. },
  2405. "files/_/_/empty.js": {
  2406. "url": "~/empty.js",
  2407. "type": "source",
  2408. "headers": {
  2409. "content-type": "application/json",
  2410. },
  2411. },
  2412. "files/_/_/file.malformed.sourcemap.js": {
  2413. "url": "~/file.malformed.sourcemap.js",
  2414. "type": "source_map",
  2415. "headers": {
  2416. "content-type": "application/json",
  2417. },
  2418. },
  2419. }
  2420. }
  2421. ),
  2422. )
  2423. compressed.seek(0)
  2424. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  2425. file.putfile(compressed)
  2426. artifact_bundle = ArtifactBundle.objects.create(
  2427. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  2428. )
  2429. ProjectArtifactBundle.objects.create(
  2430. organization_id=self.organization.id,
  2431. project_id=self.project.id,
  2432. artifact_bundle=artifact_bundle,
  2433. )
  2434. ReleaseArtifactBundle.objects.create(
  2435. organization_id=self.organization.id,
  2436. release_name=release.version,
  2437. artifact_bundle=artifact_bundle,
  2438. )
  2439. data = {
  2440. "timestamp": self.min_ago,
  2441. "message": "hello",
  2442. "platform": "javascript",
  2443. "release": release.version,
  2444. "exception": {
  2445. "values": [
  2446. {
  2447. "type": "Error",
  2448. "stacktrace": {
  2449. "frames": [
  2450. {
  2451. "abs_path": "http://example.com/file.min.js",
  2452. "filename": "file.min.js",
  2453. "lineno": 1,
  2454. "colno": 39,
  2455. },
  2456. {
  2457. "abs_path": "http://example.com/file.min.js",
  2458. "filename": "file.min.js",
  2459. "lineno": 1,
  2460. "colno": 79,
  2461. },
  2462. ]
  2463. },
  2464. }
  2465. ]
  2466. },
  2467. }
  2468. event = self.post_and_retrieve_event(data)
  2469. assert len(event.data["errors"]) == 1
  2470. assert event.data["errors"][0] == {
  2471. "type": "js_invalid_source",
  2472. "url": "http://example.com/file.malformed.sourcemap.js",
  2473. }