test_plugin.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
  1. import os.path
  2. import zipfile
  3. from base64 import b64encode
  4. from io import BytesIO
  5. from unittest.mock import patch
  6. import responses
  7. from django.utils.encoding import force_bytes
  8. from sentry.models import File, Release, ReleaseFile
  9. from sentry.models.releasefile import update_artifact_index
  10. from sentry.testutils import RelayStoreHelper, SnubaTestCase, TransactionTestCase
  11. from sentry.testutils.helpers.datetime import before_now, iso_format
  12. from sentry.utils import json
  13. BASE64_SOURCEMAP = "data:application/json;base64," + (
  14. b64encode(
  15. b'{"version":3,"file":"generated.js","sources":["/test.js"],"names":[],"mappings":"AAAA","sourcesContent":['
  16. b'"console.log(\\"hello, World!\\")"]}'
  17. )
  18. .decode("utf-8")
  19. .replace("\n", "")
  20. )
  21. INVALID_BASE64_SOURCEMAP = "data:application/json;base64,A"
  22. def get_fixture_path(name):
  23. return os.path.join(os.path.dirname(__file__), "fixtures", name)
  24. def load_fixture(name):
  25. with open(get_fixture_path(name), "rb") as fp:
  26. return fp.read()
  27. class JavascriptIntegrationTest(RelayStoreHelper, SnubaTestCase, TransactionTestCase):
  28. def setUp(self):
  29. super().setUp()
  30. self.min_ago = iso_format(before_now(minutes=1))
  31. def test_adds_contexts_without_device(self):
  32. data = {
  33. "timestamp": self.min_ago,
  34. "message": "hello",
  35. "platform": "javascript",
  36. "request": {
  37. "url": "http://example.com",
  38. "headers": [
  39. [
  40. "User-Agent",
  41. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
  42. "Chrome/28.0.1500.72 Safari/537.36",
  43. ]
  44. ],
  45. },
  46. }
  47. event = self.post_and_retrieve_event(data)
  48. contexts = event.interfaces["contexts"].to_json()
  49. assert contexts.get("os") == {"name": "Windows", "version": "8", "type": "os"}
  50. assert contexts.get("device") is None
  51. def test_adds_contexts_with_device(self):
  52. data = {
  53. "timestamp": self.min_ago,
  54. "message": "hello",
  55. "platform": "javascript",
  56. "request": {
  57. "url": "http://example.com",
  58. "headers": [
  59. [
  60. "User-Agent",
  61. "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SCH-R530U Build/JSS15J) AppleWebKit/534.30 ("
  62. "KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 USCC-R530U",
  63. ]
  64. ],
  65. },
  66. }
  67. event = self.post_and_retrieve_event(data)
  68. contexts = event.interfaces["contexts"].to_json()
  69. assert contexts.get("os") == {"name": "Android", "type": "os", "version": "4.3"}
  70. assert contexts.get("browser") == {"name": "Android", "type": "browser", "version": "4.3"}
  71. assert contexts.get("device") == {
  72. "family": "Samsung SCH-R530U",
  73. "type": "device",
  74. "model": "SCH-R530U",
  75. "brand": "Samsung",
  76. }
  77. def test_adds_contexts_with_ps4_device(self):
  78. data = {
  79. "timestamp": self.min_ago,
  80. "message": "hello",
  81. "platform": "javascript",
  82. "request": {
  83. "url": "http://example.com",
  84. "headers": [
  85. [
  86. "User-Agent",
  87. "Mozilla/5.0 (PlayStation 4 3.55) AppleWebKit/537.78 (KHTML, like Gecko)",
  88. ]
  89. ],
  90. },
  91. }
  92. event = self.post_and_retrieve_event(data)
  93. contexts = event.interfaces["contexts"].to_json()
  94. assert contexts.get("os") is None
  95. assert contexts.get("browser") is None
  96. assert contexts.get("device") == {
  97. "family": "PlayStation 4",
  98. "type": "device",
  99. "model": "PlayStation 4",
  100. "brand": "Sony",
  101. }
  102. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  103. def test_source_expansion(self, mock_fetch_by_url):
  104. data = {
  105. "timestamp": self.min_ago,
  106. "message": "hello",
  107. "platform": "javascript",
  108. "exception": {
  109. "values": [
  110. {
  111. "type": "Error",
  112. "stacktrace": {
  113. "frames": [
  114. {
  115. "abs_path": "http://example.com/foo.js",
  116. "filename": "foo.js",
  117. "lineno": 4,
  118. "colno": 0,
  119. },
  120. {
  121. "abs_path": "http://example.com/foo.js",
  122. "filename": "foo.js",
  123. "lineno": 1,
  124. "colno": 0,
  125. },
  126. ]
  127. },
  128. }
  129. ]
  130. },
  131. }
  132. mock_fetch_by_url.return_value.body = force_bytes("\n".join("hello world"))
  133. mock_fetch_by_url.return_value.encoding = None
  134. mock_fetch_by_url.return_value.headers = {}
  135. event = self.post_and_retrieve_event(data)
  136. mock_fetch_by_url.assert_called_once_with("http://example.com/foo.js")
  137. exception = event.interfaces["exception"]
  138. frame_list = exception.values[0].stacktrace.frames
  139. frame = frame_list[0]
  140. assert frame.pre_context == ["h", "e", "l"]
  141. assert frame.context_line == "l"
  142. assert frame.post_context == ["o", " ", "w", "o", "r"]
  143. frame = frame_list[1]
  144. assert not frame.pre_context
  145. assert frame.context_line == "h"
  146. assert frame.post_context == ["e", "l", "l", "o", " "]
  147. # no source map means no raw_stacktrace
  148. assert exception.values[0].raw_stacktrace is None
  149. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  150. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  151. def test_inlined_sources(self, mock_discover_sourcemap, mock_fetch_by_url):
  152. data = {
  153. "timestamp": self.min_ago,
  154. "message": "hello",
  155. "platform": "javascript",
  156. "exception": {
  157. "values": [
  158. {
  159. "type": "Error",
  160. "stacktrace": {
  161. "frames": [
  162. {
  163. "abs_path": "http://example.com/test.min.js",
  164. "filename": "test.js",
  165. "lineno": 1,
  166. "colno": 1,
  167. }
  168. ]
  169. },
  170. }
  171. ]
  172. },
  173. }
  174. mock_discover_sourcemap.return_value = BASE64_SOURCEMAP
  175. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  176. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  177. mock_fetch_by_url.return_value.encoding = None
  178. event = self.post_and_retrieve_event(data)
  179. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  180. exception = event.interfaces["exception"]
  181. frame_list = exception.values[0].stacktrace.frames
  182. frame = frame_list[0]
  183. assert not frame.pre_context
  184. assert frame.context_line == 'console.log("hello, World!")'
  185. assert not frame.post_context
  186. assert frame.data["sourcemap"] == "http://example.com/test.min.js"
  187. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  188. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  189. def test_invalid_base64_sourcemap_returns_an_error(
  190. self, mock_discover_sourcemap, mock_fetch_by_url
  191. ):
  192. data = {
  193. "timestamp": self.min_ago,
  194. "message": "hello",
  195. "platform": "javascript",
  196. "exception": {
  197. "values": [
  198. {
  199. "type": "Error",
  200. "stacktrace": {
  201. "frames": [
  202. {
  203. "abs_path": "http://example.com/test.min.js",
  204. "filename": "test.js",
  205. "lineno": 1,
  206. "colno": 1,
  207. },
  208. ]
  209. },
  210. }
  211. ]
  212. },
  213. }
  214. mock_discover_sourcemap.return_value = INVALID_BASE64_SOURCEMAP
  215. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  216. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  217. mock_fetch_by_url.return_value.encoding = None
  218. event = self.post_and_retrieve_event(data)
  219. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  220. assert len(event.data["errors"]) == 1
  221. assert event.data["errors"][0] == {
  222. "url": "<base64>",
  223. "reason": "Invalid base64-encoded string: "
  224. "number of data characters (1) cannot be 1 more than a multiple of 4",
  225. "type": "js_invalid_source",
  226. }
  227. @patch("sentry.lang.javascript.processor.SmCache.from_bytes")
  228. @patch("sentry.lang.javascript.processor.Fetcher.fetch_by_url")
  229. @patch("sentry.lang.javascript.processor.discover_sourcemap")
  230. def test_sourcemap_cache_is_constructed_only_once_if_an_error_is_raised(
  231. self, mock_discover_sourcemap, mock_fetch_by_url, mock_from_bytes
  232. ):
  233. data = {
  234. "timestamp": self.min_ago,
  235. "message": "hello",
  236. "platform": "javascript",
  237. "exception": {
  238. "values": [
  239. {
  240. "type": "Error",
  241. "stacktrace": {
  242. "frames": [
  243. {
  244. "abs_path": "http://example.com/test.min.js",
  245. "filename": "test.js",
  246. "lineno": 1,
  247. "colno": 1,
  248. },
  249. {
  250. "abs_path": "http://example.com/test.min.js",
  251. "filename": "test.js",
  252. "lineno": 1,
  253. "colno": 1,
  254. },
  255. {
  256. "abs_path": "http://example.com/test.min.js",
  257. "filename": "test.js",
  258. "lineno": 1,
  259. "colno": 1,
  260. },
  261. ]
  262. },
  263. }
  264. ]
  265. },
  266. }
  267. mock_discover_sourcemap.return_value = BASE64_SOURCEMAP
  268. mock_fetch_by_url.return_value.url = "http://example.com/test.min.js"
  269. mock_fetch_by_url.return_value.body = force_bytes("\n".join("<generated source>"))
  270. mock_fetch_by_url.return_value.encoding = None
  271. mock_from_bytes.side_effect = Exception()
  272. self.post_and_retrieve_event(data)
  273. mock_fetch_by_url.assert_called_once_with("http://example.com/test.min.js")
  274. mock_from_bytes.assert_called_once()
  275. @responses.activate
  276. def test_error_message_translations(self):
  277. data = {
  278. "timestamp": self.min_ago,
  279. "message": "hello",
  280. "platform": "javascript",
  281. "logentry": {
  282. "formatted": "ReferenceError: Impossible de d\xe9finir une propri\xe9t\xe9 \xab foo \xbb : objet non "
  283. "extensible"
  284. },
  285. "exception": {
  286. "values": [
  287. {"type": "Error", "value": "P\u0159\xedli\u0161 mnoho soubor\u016f"},
  288. {
  289. "type": "Error",
  290. "value": "foo: wyst\u0105pi\u0142 nieoczekiwany b\u0142\u0105d podczas pr\xf3by uzyskania "
  291. "informacji o metadanych",
  292. },
  293. ]
  294. },
  295. }
  296. event = self.post_and_retrieve_event(data)
  297. message = event.interfaces["logentry"]
  298. assert (
  299. message.formatted
  300. == "ReferenceError: Cannot define property 'foo': object is not extensible"
  301. )
  302. exception = event.interfaces["exception"]
  303. assert exception.values[0].value == "Too many files"
  304. assert (
  305. exception.values[1].value
  306. == "foo: an unexpected failure occurred while trying to obtain metadata information"
  307. )
  308. @responses.activate
  309. def test_sourcemap_source_expansion(self):
  310. responses.add(
  311. responses.GET,
  312. "http://example.com/file.min.js",
  313. body=load_fixture("file.min.js"),
  314. content_type="application/javascript; charset=utf-8",
  315. )
  316. responses.add(
  317. responses.GET,
  318. "http://example.com/file1.js",
  319. body=load_fixture("file1.js"),
  320. content_type="application/javascript; charset=utf-8",
  321. )
  322. responses.add(
  323. responses.GET,
  324. "http://example.com/file2.js",
  325. body=load_fixture("file2.js"),
  326. content_type="application/javascript; charset=utf-8",
  327. )
  328. responses.add(
  329. responses.GET,
  330. "http://example.com/file.sourcemap.js",
  331. body=load_fixture("file.sourcemap.js"),
  332. content_type="application/javascript; charset=utf-8",
  333. )
  334. responses.add(responses.GET, "http://example.com/index.html", body="Not Found", status=404)
  335. data = {
  336. "timestamp": self.min_ago,
  337. "message": "hello",
  338. "platform": "javascript",
  339. "exception": {
  340. "values": [
  341. {
  342. "type": "Error",
  343. "stacktrace": {
  344. "frames": [
  345. {
  346. "abs_path": "http://example.com/file.min.js",
  347. "filename": "file.min.js",
  348. "lineno": 1,
  349. "colno": 39,
  350. },
  351. # NOTE: Intentionally source is not retrieved from this HTML file
  352. {
  353. "function": 'function: "HTMLDocument.<anonymous>"',
  354. "abs_path": "http//example.com/index.html",
  355. "filename": "index.html",
  356. "lineno": 283,
  357. "colno": 17,
  358. "in_app": False,
  359. },
  360. ]
  361. },
  362. }
  363. ]
  364. },
  365. }
  366. event = self.post_and_retrieve_event(data)
  367. assert event.data["errors"] == [
  368. {"type": "js_no_source", "url": "http//example.com/index.html"}
  369. ]
  370. exception = event.interfaces["exception"]
  371. frame_list = exception.values[0].stacktrace.frames
  372. frame = frame_list[0]
  373. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  374. expected = "\treturn a + b; // fôo"
  375. assert frame.context_line == expected
  376. assert frame.post_context == ["}", ""]
  377. raw_frame_list = exception.values[0].raw_stacktrace.frames
  378. raw_frame = raw_frame_list[0]
  379. assert not raw_frame.pre_context
  380. assert (
  381. raw_frame.context_line
  382. == 'function add(a,b){"use strict";return a+b}function multiply(a,b){"use strict";return a*b}function '
  383. 'divide(a,b){"use strict";try{return multip {snip}'
  384. )
  385. assert raw_frame.post_context == ["//@ sourceMappingURL=file.sourcemap.js", ""]
  386. assert raw_frame.lineno == 1
  387. # Since we couldn't expand source for the 2nd frame, both
  388. # its raw and original form should be identical
  389. assert raw_frame_list[1] == frame_list[1]
  390. @responses.activate
  391. def test_sourcemap_ignore_debug_id(self):
  392. # This tests the same as the one above, but it ensures that for now the debug_id
  393. # code that is not yet implemented, will be not make any damage yet.
  394. responses.add(
  395. responses.GET,
  396. "http://example.com/file.min.js",
  397. body=load_fixture("file.min.js"),
  398. content_type="application/javascript; charset=utf-8",
  399. )
  400. responses.add(
  401. responses.GET,
  402. "http://example.com/file1.js",
  403. body=load_fixture("file1.js"),
  404. content_type="application/javascript; charset=utf-8",
  405. )
  406. responses.add(
  407. responses.GET,
  408. "http://example.com/file2.js",
  409. body=load_fixture("file2.js"),
  410. content_type="application/javascript; charset=utf-8",
  411. )
  412. responses.add(
  413. responses.GET,
  414. "http://example.com/file.sourcemap.js",
  415. body=load_fixture("file.sourcemap.js"),
  416. content_type="application/javascript; charset=utf-8",
  417. )
  418. responses.add(responses.GET, "http://example.com/index.html", body="Not Found", status=404)
  419. data = {
  420. "timestamp": self.min_ago,
  421. "message": "hello",
  422. "platform": "javascript",
  423. "debug_meta": {
  424. "images": [
  425. {
  426. "type": "sourcemap",
  427. "debug_id": "c941d872-af1f-4f0c-a7ff-ad3d295fe153",
  428. "code_file": "http://example.com/file.min.js",
  429. }
  430. ]
  431. },
  432. "exception": {
  433. "values": [
  434. {
  435. "type": "Error",
  436. "stacktrace": {
  437. "frames": [
  438. {
  439. "abs_path": "http://example.com/file.min.js",
  440. "filename": "file.min.js",
  441. "lineno": 1,
  442. "colno": 39,
  443. },
  444. # NOTE: Intentionally source is not retrieved from this HTML file
  445. {
  446. "function": 'function: "HTMLDocument.<anonymous>"',
  447. "abs_path": "http//example.com/index.html",
  448. "filename": "index.html",
  449. "lineno": 283,
  450. "colno": 17,
  451. "in_app": False,
  452. },
  453. ]
  454. },
  455. }
  456. ]
  457. },
  458. }
  459. event = self.post_and_retrieve_event(data)
  460. assert event.data["errors"] == [
  461. {"type": "js_no_source", "url": "http//example.com/index.html"}
  462. ]
  463. exception = event.interfaces["exception"]
  464. frame_list = exception.values[0].stacktrace.frames
  465. frame = frame_list[0]
  466. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  467. expected = "\treturn a + b; // fôo"
  468. assert frame.context_line == expected
  469. assert frame.post_context == ["}", ""]
  470. raw_frame_list = exception.values[0].raw_stacktrace.frames
  471. raw_frame = raw_frame_list[0]
  472. assert not raw_frame.pre_context
  473. assert (
  474. raw_frame.context_line
  475. == 'function add(a,b){"use strict";return a+b}function multiply(a,b){"use strict";return a*b}function '
  476. 'divide(a,b){"use strict";try{return multip {snip}'
  477. )
  478. assert raw_frame.post_context == ["//@ sourceMappingURL=file.sourcemap.js", ""]
  479. assert raw_frame.lineno == 1
  480. # Since we couldn't expand source for the 2nd frame, both
  481. # its raw and original form should be identical
  482. assert raw_frame_list[1] == frame_list[1]
  483. @responses.activate
  484. def test_sourcemap_embedded_source_expansion(self):
  485. responses.add(
  486. responses.GET,
  487. "http://example.com/embedded.js",
  488. body=load_fixture("embedded.js"),
  489. content_type="application/javascript; charset=utf-8",
  490. )
  491. responses.add(
  492. responses.GET,
  493. "http://example.com/embedded.js.map",
  494. body=load_fixture("embedded.js.map"),
  495. content_type="application/json; charset=utf-8",
  496. )
  497. responses.add(responses.GET, "http://example.com/index.html", body="Not Found", status=404)
  498. data = {
  499. "timestamp": self.min_ago,
  500. "message": "hello",
  501. "platform": "javascript",
  502. "exception": {
  503. "values": [
  504. {
  505. "type": "Error",
  506. "stacktrace": {
  507. "frames": [
  508. {
  509. "abs_path": "http://example.com/embedded.js",
  510. "filename": "file.min.js",
  511. "lineno": 1,
  512. "colno": 39,
  513. },
  514. # NOTE: Intentionally source is not retrieved from this HTML file
  515. {
  516. "function": 'function: "HTMLDocument.<anonymous>"',
  517. "abs_path": "http//example.com/index.html",
  518. "filename": "index.html",
  519. "lineno": 283,
  520. "colno": 17,
  521. "in_app": False,
  522. },
  523. ]
  524. },
  525. }
  526. ]
  527. },
  528. }
  529. event = self.post_and_retrieve_event(data)
  530. assert event.data["errors"] == [
  531. {"type": "js_no_source", "url": "http//example.com/index.html"}
  532. ]
  533. exception = event.interfaces["exception"]
  534. frame_list = exception.values[0].stacktrace.frames
  535. frame = frame_list[0]
  536. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  537. expected = "\treturn a + b; // fôo"
  538. assert frame.context_line == expected
  539. assert frame.post_context == ["}", ""]
  540. @responses.activate
  541. def test_sourcemap_nofiles_source_expansion(self):
  542. project = self.project
  543. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  544. release.add_project(project)
  545. with open(get_fixture_path("nofiles.js"), "rb") as f:
  546. f_minified = File.objects.create(
  547. name="nofiles.js", type="release.file", headers={"Content-Type": "application/json"}
  548. )
  549. f_minified.putfile(f)
  550. ReleaseFile.objects.create(
  551. name=f"~/{f_minified.name}",
  552. release_id=release.id,
  553. organization_id=project.organization_id,
  554. file=f_minified,
  555. )
  556. with open(get_fixture_path("nofiles.js.map"), "rb") as f:
  557. f_sourcemap = File.objects.create(
  558. name="nofiles.js.map",
  559. type="release.file",
  560. headers={"Content-Type": "application/json"},
  561. )
  562. f_sourcemap.putfile(f)
  563. ReleaseFile.objects.create(
  564. name=f"app:///{f_sourcemap.name}",
  565. release_id=release.id,
  566. organization_id=project.organization_id,
  567. file=f_sourcemap,
  568. )
  569. data = {
  570. "timestamp": self.min_ago,
  571. "message": "hello",
  572. "platform": "javascript",
  573. "release": "abc",
  574. "exception": {
  575. "values": [
  576. {
  577. "type": "Error",
  578. "stacktrace": {
  579. "frames": [{"abs_path": "app:///nofiles.js", "lineno": 1, "colno": 39}]
  580. },
  581. }
  582. ]
  583. },
  584. }
  585. event = self.post_and_retrieve_event(data)
  586. assert "errors" not in event.data
  587. exception = event.interfaces["exception"]
  588. frame_list = exception.values[0].stacktrace.frames
  589. assert len(frame_list) == 1
  590. frame = frame_list[0]
  591. assert frame.abs_path == "app:///nofiles.js"
  592. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  593. assert frame.context_line == "\treturn a + b; // fôo"
  594. assert frame.post_context == ["}", ""]
  595. @responses.activate
  596. def test_indexed_sourcemap_source_expansion(self):
  597. responses.add(
  598. responses.GET,
  599. "http://example.com/indexed.min.js",
  600. body=load_fixture("indexed.min.js"),
  601. content_type="application/javascript; charset=utf-8",
  602. )
  603. responses.add(
  604. responses.GET,
  605. "http://example.com/file1.js",
  606. body=load_fixture("file1.js"),
  607. content_type="application/javascript; charset=utf-8",
  608. )
  609. responses.add(
  610. responses.GET,
  611. "http://example.com/file2.js",
  612. body=load_fixture("file2.js"),
  613. content_type="application/javascript; charset=utf-8",
  614. )
  615. responses.add(
  616. responses.GET,
  617. "http://example.com/indexed.sourcemap.js",
  618. body=load_fixture("indexed.sourcemap.js"),
  619. content_type="application/json; charset=utf-8",
  620. )
  621. data = {
  622. "timestamp": self.min_ago,
  623. "message": "hello",
  624. "platform": "javascript",
  625. "exception": {
  626. "values": [
  627. {
  628. "type": "Error",
  629. "stacktrace": {
  630. "frames": [
  631. {
  632. "abs_path": "http://example.com/indexed.min.js",
  633. "filename": "indexed.min.js",
  634. "lineno": 1,
  635. "colno": 39,
  636. },
  637. {
  638. "abs_path": "http://example.com/indexed.min.js",
  639. "filename": "indexed.min.js",
  640. "lineno": 2,
  641. "colno": 44,
  642. },
  643. ]
  644. },
  645. }
  646. ]
  647. },
  648. }
  649. event = self.post_and_retrieve_event(data)
  650. assert "errors" not in event.data
  651. exception = event.interfaces["exception"]
  652. frame_list = exception.values[0].stacktrace.frames
  653. frame = frame_list[0]
  654. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  655. expected = "\treturn a + b; // fôo"
  656. assert frame.context_line == expected
  657. assert frame.post_context == ["}", ""]
  658. raw_frame_list = exception.values[0].raw_stacktrace.frames
  659. raw_frame = raw_frame_list[0]
  660. assert not raw_frame.pre_context
  661. assert raw_frame.context_line == 'function add(a,b){"use strict";return a+b}'
  662. assert raw_frame.post_context == [
  663. 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  664. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}",
  665. "//# sourceMappingURL=indexed.sourcemap.js",
  666. "",
  667. ]
  668. assert raw_frame.lineno == 1
  669. frame = frame_list[1]
  670. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  671. assert frame.context_line == "\treturn a * b;"
  672. assert frame.post_context == [
  673. "}",
  674. "function divide(a, b) {",
  675. '\t"use strict";',
  676. "\ttry {",
  677. "\t\treturn multiply(add(a, b), a, b) / c;",
  678. ]
  679. raw_frame = raw_frame_list[1]
  680. assert raw_frame.pre_context == ['function add(a,b){"use strict";return a+b}']
  681. assert (
  682. raw_frame.context_line
  683. == 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  684. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}"
  685. )
  686. assert raw_frame.post_context == ["//# sourceMappingURL=indexed.sourcemap.js", ""]
  687. assert raw_frame.lineno == 2
  688. @responses.activate
  689. def test_expansion_via_release_artifacts(self):
  690. project = self.project
  691. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  692. release.add_project(project)
  693. # file.min.js
  694. # ------------
  695. with open(get_fixture_path("file.min.js"), "rb") as f:
  696. f_minified = File.objects.create(
  697. name="file.min.js",
  698. type="release.file",
  699. headers={"Content-Type": "application/json"},
  700. )
  701. f_minified.putfile(f)
  702. # Intentionally omit hostname - use alternate artifact path lookup instead
  703. # /file1.js vs http://example.com/file1.js
  704. ReleaseFile.objects.create(
  705. name=f"~/{f_minified.name}?foo=bar",
  706. release_id=release.id,
  707. organization_id=project.organization_id,
  708. file=f_minified,
  709. )
  710. # file1.js
  711. # ---------
  712. with open(get_fixture_path("file1.js"), "rb") as f:
  713. f1 = File.objects.create(
  714. name="file1.js", type="release.file", headers={"Content-Type": "application/json"}
  715. )
  716. f1.putfile(f)
  717. ReleaseFile.objects.create(
  718. name=f"http://example.com/{f1.name}",
  719. release_id=release.id,
  720. organization_id=project.organization_id,
  721. file=f1,
  722. )
  723. # file2.js
  724. # ----------
  725. with open(get_fixture_path("file2.js"), "rb") as f:
  726. f2 = File.objects.create(
  727. name="file2.js", type="release.file", headers={"Content-Type": "application/json"}
  728. )
  729. f2.putfile(f)
  730. ReleaseFile.objects.create(
  731. name=f"http://example.com/{f2.name}",
  732. release_id=release.id,
  733. organization_id=project.organization_id,
  734. file=f2,
  735. )
  736. # To verify that the full url has priority over the relative url,
  737. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  738. # hostname that points to an empty file. If the processor chooses
  739. # this empty file over the correct file2.js, it will not locate
  740. # context for the 2nd frame.
  741. with open(get_fixture_path("empty.js"), "rb") as f:
  742. f2_empty = File.objects.create(
  743. name="empty.js", type="release.file", headers={"Content-Type": "application/json"}
  744. )
  745. f2_empty.putfile(f)
  746. ReleaseFile.objects.create(
  747. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  748. release_id=release.id,
  749. organization_id=project.organization_id,
  750. file=f2_empty,
  751. )
  752. # sourcemap
  753. # ----------
  754. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  755. f_sourcemap = File.objects.create(
  756. name="file.sourcemap.js",
  757. type="release.file",
  758. headers={"Content-Type": "application/json"},
  759. )
  760. f_sourcemap.putfile(f)
  761. ReleaseFile.objects.create(
  762. name=f"http://example.com/{f_sourcemap.name}",
  763. release_id=release.id,
  764. organization_id=project.organization_id,
  765. file=f_sourcemap,
  766. )
  767. data = {
  768. "timestamp": self.min_ago,
  769. "message": "hello",
  770. "platform": "javascript",
  771. "release": "abc",
  772. "exception": {
  773. "values": [
  774. {
  775. "type": "Error",
  776. "stacktrace": {
  777. "frames": [
  778. {
  779. "abs_path": "http://example.com/file.min.js?foo=bar",
  780. "filename": "file.min.js",
  781. "lineno": 1,
  782. "colno": 39,
  783. },
  784. {
  785. "abs_path": "http://example.com/file.min.js?foo=bar",
  786. "filename": "file.min.js",
  787. "lineno": 1,
  788. "colno": 79,
  789. },
  790. ]
  791. },
  792. }
  793. ]
  794. },
  795. }
  796. event = self.post_and_retrieve_event(data)
  797. assert "errors" not in event.data
  798. exception = event.interfaces["exception"]
  799. frame_list = exception.values[0].stacktrace.frames
  800. frame = frame_list[0]
  801. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  802. assert frame.context_line == "\treturn a + b; // fôo"
  803. assert frame.post_context == ["}", ""]
  804. frame = frame_list[1]
  805. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  806. assert frame.context_line == "\treturn a * b;"
  807. assert frame.post_context == [
  808. "}",
  809. "function divide(a, b) {",
  810. '\t"use strict";',
  811. "\ttry {",
  812. "\t\treturn multiply(add(a, b), a, b) / c;",
  813. ]
  814. @responses.activate
  815. def test_expansion_via_distribution_release_artifacts(self):
  816. project = self.project
  817. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  818. release.add_project(project)
  819. dist = release.add_dist("foo")
  820. # file.min.js
  821. # ------------
  822. with open(get_fixture_path("file.min.js"), "rb") as f:
  823. f_minified = File.objects.create(
  824. name="file.min.js",
  825. type="release.file",
  826. headers={"Content-Type": "application/json"},
  827. )
  828. f_minified.putfile(f)
  829. # Intentionally omit hostname - use alternate artifact path lookup instead
  830. # /file1.js vs http://example.com/file1.js
  831. ReleaseFile.objects.create(
  832. name=f"~/{f_minified.name}?foo=bar",
  833. release_id=release.id,
  834. dist_id=dist.id,
  835. organization_id=project.organization_id,
  836. file=f_minified,
  837. )
  838. # file1.js
  839. # ---------
  840. with open(get_fixture_path("file1.js"), "rb") as f:
  841. f1 = File.objects.create(
  842. name="file1.js", type="release.file", headers={"Content-Type": "application/json"}
  843. )
  844. f1.putfile(f)
  845. ReleaseFile.objects.create(
  846. name=f"http://example.com/{f1.name}",
  847. release_id=release.id,
  848. dist_id=dist.id,
  849. organization_id=project.organization_id,
  850. file=f1,
  851. )
  852. # file2.js
  853. # ----------
  854. with open(get_fixture_path("file2.js"), "rb") as f:
  855. f2 = File.objects.create(
  856. name="file2.js", type="release.file", headers={"Content-Type": "application/json"}
  857. )
  858. f2.putfile(f)
  859. ReleaseFile.objects.create(
  860. name=f"http://example.com/{f2.name}",
  861. release_id=release.id,
  862. dist_id=dist.id,
  863. organization_id=project.organization_id,
  864. file=f2,
  865. )
  866. # To verify that the full url has priority over the relative url,
  867. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  868. # hostname that points to an empty file. If the processor chooses
  869. # this empty file over the correct file2.js, it will not locate
  870. # context for the 2nd frame.
  871. with open(get_fixture_path("empty.js"), "rb") as f:
  872. f2_empty = File.objects.create(
  873. name="empty.js", type="release.file", headers={"Content-Type": "application/json"}
  874. )
  875. f2_empty.putfile(f)
  876. ReleaseFile.objects.create(
  877. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  878. release_id=release.id,
  879. dist_id=dist.id,
  880. organization_id=project.organization_id,
  881. file=f2_empty,
  882. )
  883. # sourcemap
  884. # ----------
  885. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  886. f_sourcemap = File.objects.create(
  887. name="file.sourcemap.js",
  888. type="release.file",
  889. headers={"Content-Type": "application/json"},
  890. )
  891. f_sourcemap.putfile(f)
  892. ReleaseFile.objects.create(
  893. name=f"http://example.com/{f_sourcemap.name}",
  894. release_id=release.id,
  895. dist_id=dist.id,
  896. organization_id=project.organization_id,
  897. file=f_sourcemap,
  898. )
  899. data = {
  900. "timestamp": self.min_ago,
  901. "message": "hello",
  902. "platform": "javascript",
  903. "release": "abc",
  904. "dist": "foo",
  905. "exception": {
  906. "values": [
  907. {
  908. "type": "Error",
  909. "stacktrace": {
  910. "frames": [
  911. {
  912. "abs_path": "http://example.com/file.min.js?foo=bar",
  913. "filename": "file.min.js",
  914. "lineno": 1,
  915. "colno": 39,
  916. },
  917. {
  918. "abs_path": "http://example.com/file.min.js?foo=bar",
  919. "filename": "file.min.js",
  920. "lineno": 1,
  921. "colno": 79,
  922. },
  923. ]
  924. },
  925. }
  926. ]
  927. },
  928. }
  929. event = self.post_and_retrieve_event(data)
  930. assert "errors" not in event.data
  931. exception = event.interfaces["exception"]
  932. frame_list = exception.values[0].stacktrace.frames
  933. frame = frame_list[0]
  934. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  935. assert frame.context_line == "\treturn a + b; // fôo"
  936. assert frame.post_context == ["}", ""]
  937. frame = frame_list[1]
  938. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  939. assert frame.context_line == "\treturn a * b;"
  940. assert frame.post_context == [
  941. "}",
  942. "function divide(a, b) {",
  943. '\t"use strict";',
  944. "\ttry {",
  945. "\t\treturn multiply(add(a, b), a, b) / c;",
  946. ]
  947. @responses.activate
  948. def test_sourcemap_expansion_with_missing_source(self):
  949. """
  950. Tests a successful sourcemap expansion that points to source files
  951. that are not found.
  952. """
  953. responses.add(
  954. responses.GET,
  955. "http://example.com/file.min.js",
  956. body=load_fixture("file.min.js"),
  957. content_type="application/javascript; charset=utf-8",
  958. )
  959. responses.add(
  960. responses.GET,
  961. "http://example.com/file.sourcemap.js",
  962. body=load_fixture("file.sourcemap.js"),
  963. content_type="application/json; charset=utf-8",
  964. )
  965. responses.add(responses.GET, "http://example.com/file1.js", body="Not Found", status=404)
  966. data = {
  967. "timestamp": self.min_ago,
  968. "message": "hello",
  969. "platform": "javascript",
  970. "exception": {
  971. "values": [
  972. {
  973. "type": "Error",
  974. "stacktrace": {
  975. # Add two frames. We only want to see the
  976. # error once though.
  977. "frames": [
  978. {
  979. "abs_path": "http://example.com/file.min.js",
  980. "filename": "file.min.js",
  981. "lineno": 1,
  982. "colno": 39,
  983. },
  984. {
  985. "abs_path": "http://example.com/file.min.js",
  986. "filename": "file.min.js",
  987. "lineno": 1,
  988. "colno": 39,
  989. },
  990. ]
  991. },
  992. }
  993. ]
  994. },
  995. }
  996. event = self.post_and_retrieve_event(data)
  997. assert event.data["errors"] == [
  998. {"url": "http://example.com/file1.js", "type": "fetch_invalid_http_code", "value": 404}
  999. ]
  1000. exception = event.interfaces["exception"]
  1001. frame_list = exception.values[0].stacktrace.frames
  1002. frame = frame_list[0]
  1003. # no context information ...
  1004. assert not frame.pre_context
  1005. assert not frame.context_line
  1006. assert not frame.post_context
  1007. # ... but line, column numbers are still correctly mapped
  1008. assert frame.lineno == 3
  1009. assert frame.colno == 9
  1010. @responses.activate
  1011. def test_failed_sourcemap_expansion(self):
  1012. """
  1013. Tests attempting to parse an indexed source map where each section has a "url"
  1014. property - this is unsupported and should fail.
  1015. """
  1016. responses.add(
  1017. responses.GET,
  1018. "http://example.com/unsupported.min.js",
  1019. body=load_fixture("unsupported.min.js"),
  1020. content_type="application/javascript; charset=utf-8",
  1021. )
  1022. responses.add(
  1023. responses.GET,
  1024. "http://example.com/unsupported.sourcemap.js",
  1025. body=load_fixture("unsupported.sourcemap.js"),
  1026. content_type="application/json; charset=utf-8",
  1027. )
  1028. data = {
  1029. "timestamp": self.min_ago,
  1030. "message": "hello",
  1031. "platform": "javascript",
  1032. "exception": {
  1033. "values": [
  1034. {
  1035. "type": "Error",
  1036. "stacktrace": {
  1037. "frames": [
  1038. {
  1039. "abs_path": "http://example.com/unsupported.min.js",
  1040. "filename": "indexed.min.js",
  1041. "lineno": 1,
  1042. "colno": 39,
  1043. }
  1044. ]
  1045. },
  1046. }
  1047. ]
  1048. },
  1049. }
  1050. event = self.post_and_retrieve_event(data)
  1051. assert event.data["errors"] == [
  1052. {"url": "http://example.com/unsupported.sourcemap.js", "type": "js_invalid_source"}
  1053. ]
  1054. def test_failed_sourcemap_expansion_data_url(self):
  1055. data = {
  1056. "timestamp": self.min_ago,
  1057. "message": "hello",
  1058. "platform": "javascript",
  1059. "exception": {
  1060. "values": [
  1061. {
  1062. "type": "Error",
  1063. "stacktrace": {
  1064. "frames": [
  1065. {
  1066. "abs_path": "data:application/javascript,base46,asfasf",
  1067. "filename": "indexed.min.js",
  1068. "lineno": 1,
  1069. "colno": 39,
  1070. }
  1071. ]
  1072. },
  1073. }
  1074. ]
  1075. },
  1076. }
  1077. event = self.post_and_retrieve_event(data)
  1078. assert event.data["errors"] == [{"url": "<data url>", "type": "js_no_source"}]
  1079. @responses.activate
  1080. def test_failed_sourcemap_expansion_missing_location_entirely(self):
  1081. responses.add(
  1082. responses.GET,
  1083. "http://example.com/indexed.min.js",
  1084. body="//# sourceMappingURL=indexed.sourcemap.js",
  1085. )
  1086. responses.add(responses.GET, "http://example.com/indexed.sourcemap.js", body="{}")
  1087. data = {
  1088. "timestamp": self.min_ago,
  1089. "message": "hello",
  1090. "platform": "javascript",
  1091. "exception": {
  1092. "values": [
  1093. {
  1094. "type": "Error",
  1095. "stacktrace": {
  1096. "frames": [
  1097. {
  1098. "abs_path": "http://example.com/indexed.min.js",
  1099. "filename": "indexed.min.js",
  1100. "lineno": 1,
  1101. "colno": 1,
  1102. },
  1103. {
  1104. "abs_path": "http://example.com/indexed.min.js",
  1105. "filename": "indexed.min.js",
  1106. },
  1107. ]
  1108. },
  1109. }
  1110. ]
  1111. },
  1112. }
  1113. event = self.post_and_retrieve_event(data)
  1114. assert "errors" not in event.data
  1115. @responses.activate
  1116. def test_html_response_for_js(self):
  1117. responses.add(
  1118. responses.GET,
  1119. "http://example.com/file1.js",
  1120. body=" <!DOCTYPE html><html><head></head><body></body></html>",
  1121. )
  1122. responses.add(
  1123. responses.GET,
  1124. "http://example.com/file2.js",
  1125. body="<!doctype html><html><head></head><body></body></html>",
  1126. )
  1127. responses.add(
  1128. responses.GET,
  1129. "http://example.com/file.html",
  1130. body=(
  1131. "<!doctype html><html><head></head><body><script>/*legit case*/</script></body></html>"
  1132. ),
  1133. )
  1134. data = {
  1135. "timestamp": self.min_ago,
  1136. "message": "hello",
  1137. "platform": "javascript",
  1138. "exception": {
  1139. "values": [
  1140. {
  1141. "type": "Error",
  1142. "stacktrace": {
  1143. "frames": [
  1144. {
  1145. "abs_path": "http://example.com/file1.js",
  1146. "filename": "file.min.js",
  1147. "lineno": 1,
  1148. "colno": 39,
  1149. },
  1150. {
  1151. "abs_path": "http://example.com/file2.js",
  1152. "filename": "file.min.js",
  1153. "lineno": 1,
  1154. "colno": 39,
  1155. },
  1156. {
  1157. "abs_path": "http://example.com/file.html",
  1158. "filename": "file.html",
  1159. "lineno": 1,
  1160. "colno": 1,
  1161. },
  1162. ]
  1163. },
  1164. }
  1165. ]
  1166. },
  1167. }
  1168. event = self.post_and_retrieve_event(data)
  1169. assert event.data["errors"] == [
  1170. {"url": "http://example.com/file1.js", "type": "js_invalid_content"},
  1171. {"url": "http://example.com/file2.js", "type": "js_invalid_content"},
  1172. ]
  1173. def _test_expansion_via_release_archive(self, link_sourcemaps: bool):
  1174. project = self.project
  1175. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1176. release.add_project(project)
  1177. manifest = {
  1178. "org": self.organization.slug,
  1179. "release": release.version,
  1180. "files": {
  1181. "files/_/_/file.min.js": {
  1182. "url": "http://example.com/file.min.js",
  1183. },
  1184. "files/_/_/file1.js": {
  1185. "url": "http://example.com/file1.js",
  1186. },
  1187. "files/_/_/file2.js": {
  1188. "url": "http://example.com/file2.js",
  1189. },
  1190. "files/_/_/file.sourcemap.js": {
  1191. "url": "http://example.com/file.sourcemap.js",
  1192. },
  1193. },
  1194. }
  1195. file_like = BytesIO()
  1196. with zipfile.ZipFile(file_like, "w") as zip:
  1197. for rel_path, entry in manifest["files"].items():
  1198. name = os.path.basename(rel_path)
  1199. content = load_fixture(name)
  1200. if name == "file.min.js" and not link_sourcemaps:
  1201. # Remove link to source map, add to header instead
  1202. content = content.replace(b"//@ sourceMappingURL=file.sourcemap.js", b"")
  1203. entry["headers"] = {"SourceMap": "/file.sourcemap.js"}
  1204. zip.writestr(rel_path, content)
  1205. zip.writestr("manifest.json", json.dumps(manifest))
  1206. file_like.seek(0)
  1207. file = File.objects.create(name="doesnt_matter", type="release.bundle")
  1208. file.putfile(file_like)
  1209. update_artifact_index(release, None, file)
  1210. data = {
  1211. "timestamp": self.min_ago,
  1212. "message": "hello",
  1213. "platform": "javascript",
  1214. "release": "abc",
  1215. "exception": {
  1216. "values": [
  1217. {
  1218. "type": "Error",
  1219. "stacktrace": {
  1220. "frames": [
  1221. {
  1222. "abs_path": "http://example.com/file.min.js",
  1223. "filename": "file.min.js",
  1224. "lineno": 1,
  1225. "colno": 39,
  1226. },
  1227. {
  1228. "abs_path": "http://example.com/file.min.js",
  1229. "filename": "file.min.js",
  1230. "lineno": 1,
  1231. "colno": 79,
  1232. },
  1233. ]
  1234. },
  1235. }
  1236. ]
  1237. },
  1238. }
  1239. event = self.post_and_retrieve_event(data)
  1240. assert "errors" not in event.data
  1241. exception = event.interfaces["exception"]
  1242. frame_list = exception.values[0].stacktrace.frames
  1243. frame = frame_list[0]
  1244. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1245. assert frame.context_line == "\treturn a + b; // fôo"
  1246. assert frame.post_context == ["}", ""]
  1247. frame = frame_list[1]
  1248. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1249. assert frame.context_line == "\treturn a * b;"
  1250. assert frame.post_context == [
  1251. "}",
  1252. "function divide(a, b) {",
  1253. '\t"use strict";',
  1254. "\ttry {",
  1255. "\t\treturn multiply(add(a, b), a, b) / c;",
  1256. ]
  1257. def test_expansion_via_release_archive(self):
  1258. self._test_expansion_via_release_archive(link_sourcemaps=True)
  1259. def test_expansion_via_release_archive_no_sourcemap_link(self):
  1260. self._test_expansion_via_release_archive(link_sourcemaps=False)
  1261. def test_node_processing(self):
  1262. project = self.project
  1263. release = Release.objects.create(
  1264. organization_id=project.organization_id, version="nodeabc123"
  1265. )
  1266. release.add_project(project)
  1267. with open(get_fixture_path("dist.bundle.js"), "rb") as f:
  1268. f_minified = File.objects.create(
  1269. name="dist.bundle.js",
  1270. type="release.file",
  1271. headers={"Content-Type": "application/javascript"},
  1272. )
  1273. f_minified.putfile(f)
  1274. ReleaseFile.objects.create(
  1275. name=f"~/{f_minified.name}",
  1276. release_id=release.id,
  1277. organization_id=project.organization_id,
  1278. file=f_minified,
  1279. )
  1280. with open(get_fixture_path("dist.bundle.js.map"), "rb") as f:
  1281. f_sourcemap = File.objects.create(
  1282. name="dist.bundle.js.map",
  1283. type="release.file",
  1284. headers={"Content-Type": "application/javascript"},
  1285. )
  1286. f_sourcemap.putfile(f)
  1287. ReleaseFile.objects.create(
  1288. name=f"~/{f_sourcemap.name}",
  1289. release_id=release.id,
  1290. organization_id=project.organization_id,
  1291. file=f_sourcemap,
  1292. )
  1293. data = {
  1294. "timestamp": self.min_ago,
  1295. "message": "hello",
  1296. "platform": "node",
  1297. "release": "nodeabc123",
  1298. "exception": {
  1299. "values": [
  1300. {
  1301. "type": "Error",
  1302. "stacktrace": {
  1303. "frames": [
  1304. {
  1305. "filename": "app:///dist.bundle.js",
  1306. "function": "bar",
  1307. "lineno": 9,
  1308. "colno": 2321,
  1309. },
  1310. {
  1311. "filename": "app:///dist.bundle.js",
  1312. "function": "foo",
  1313. "lineno": 3,
  1314. "colno": 2308,
  1315. },
  1316. {
  1317. "filename": "app:///dist.bundle.js",
  1318. "function": "App",
  1319. "lineno": 3,
  1320. "colno": 1011,
  1321. },
  1322. {
  1323. "filename": "app:///dist.bundle.js",
  1324. "function": "Object.<anonymous>",
  1325. "lineno": 1,
  1326. "colno": 1014,
  1327. },
  1328. {
  1329. "filename": "app:///dist.bundle.js",
  1330. "function": "__webpack_require__",
  1331. "lineno": 20,
  1332. "colno": 30,
  1333. },
  1334. {
  1335. "filename": "app:///dist.bundle.js",
  1336. "function": "<unknown>",
  1337. "lineno": 18,
  1338. "colno": 63,
  1339. },
  1340. ]
  1341. },
  1342. }
  1343. ]
  1344. },
  1345. }
  1346. event = self.post_and_retrieve_event(data)
  1347. exception = event.interfaces["exception"]
  1348. frame_list = exception.values[0].stacktrace.frames
  1349. assert len(frame_list) == 6
  1350. assert frame_list[0].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1351. assert frame_list[0].function == "bar"
  1352. assert frame_list[0].lineno == 8
  1353. assert frame_list[1].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1354. assert frame_list[1].function == "foo"
  1355. assert frame_list[1].lineno == 2
  1356. assert frame_list[2].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1357. assert frame_list[2].function == "App"
  1358. assert frame_list[2].lineno == 2
  1359. assert frame_list[3].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1360. assert frame_list[3].function == "Object.<anonymous>"
  1361. assert frame_list[3].lineno == 1
  1362. assert frame_list[4].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1363. assert frame_list[4].function == "__webpack_require__"
  1364. assert frame_list[4].lineno == 19
  1365. assert frame_list[5].abs_path == "webpack:///webpack/bootstrap d9a5a31d9276b73873d3"
  1366. assert frame_list[5].function == "<unknown>"
  1367. assert frame_list[5].lineno == 16
  1368. @responses.activate
  1369. def test_no_fetch_from_http(self):
  1370. responses.add(
  1371. responses.GET,
  1372. "http://example.com/node_app.min.js",
  1373. body=load_fixture("node_app.min.js"),
  1374. content_type="application/javascript; charset=utf-8",
  1375. )
  1376. responses.add(
  1377. responses.GET,
  1378. "http://example.com/node_app.min.js.map",
  1379. body=load_fixture("node_app.min.js.map"),
  1380. content_type="application/javascript; charset=utf-8",
  1381. )
  1382. data = {
  1383. "timestamp": self.min_ago,
  1384. "message": "hello",
  1385. "platform": "node",
  1386. "exception": {
  1387. "values": [
  1388. {
  1389. "type": "Error",
  1390. "stacktrace": {
  1391. "frames": [
  1392. {
  1393. "abs_path": "node_bootstrap.js",
  1394. "filename": "node_bootstrap.js",
  1395. "lineno": 1,
  1396. "colno": 38,
  1397. },
  1398. {
  1399. "abs_path": "timers.js",
  1400. "filename": "timers.js",
  1401. "lineno": 1,
  1402. "colno": 39,
  1403. },
  1404. {
  1405. "abs_path": "webpack:///internal",
  1406. "filename": "internal",
  1407. "lineno": 1,
  1408. "colno": 43,
  1409. },
  1410. {
  1411. "abs_path": "webpack:///~/some_dep/file.js",
  1412. "filename": "file.js",
  1413. "lineno": 1,
  1414. "colno": 41,
  1415. },
  1416. {
  1417. "abs_path": "webpack:///./node_modules/file.js",
  1418. "filename": "file.js",
  1419. "lineno": 1,
  1420. "colno": 42,
  1421. },
  1422. {
  1423. "abs_path": "http://example.com/node_app.min.js",
  1424. "filename": "node_app.min.js",
  1425. "lineno": 1,
  1426. "colno": 40,
  1427. },
  1428. ]
  1429. },
  1430. }
  1431. ]
  1432. },
  1433. }
  1434. event = self.post_and_retrieve_event(data)
  1435. exception = event.interfaces["exception"]
  1436. frame_list = exception.values[0].stacktrace.frames
  1437. # This one should not process, so this one should be none.
  1438. assert exception.values[0].raw_stacktrace is None
  1439. # None of the in app should update
  1440. for x in range(6):
  1441. assert not frame_list[x].in_app
  1442. @responses.activate
  1443. def test_html_file_with_query_param_ending_with_js_extension(self):
  1444. responses.add(
  1445. responses.GET,
  1446. "http://example.com/file.html",
  1447. body=(
  1448. "<!doctype html><html><head></head><body><script>/*legit case*/</script></body></html>"
  1449. ),
  1450. )
  1451. data = {
  1452. "timestamp": self.min_ago,
  1453. "message": "hello",
  1454. "platform": "javascript",
  1455. "exception": {
  1456. "values": [
  1457. {
  1458. "type": "Error",
  1459. "stacktrace": {
  1460. "frames": [
  1461. {
  1462. "abs_path": "http://example.com/file.html?sw=iddqd1337.js",
  1463. "filename": "file.html",
  1464. "lineno": 1,
  1465. "colno": 1,
  1466. },
  1467. ]
  1468. },
  1469. }
  1470. ]
  1471. },
  1472. }
  1473. event = self.post_and_retrieve_event(data)
  1474. assert "errors" not in event.data