test_plugin.py 96 KB

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