getClientErrorObject.test.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import {
  20. COMMON_ERR_MESSAGES,
  21. getClientErrorMessage,
  22. getClientErrorObject,
  23. getErrorText,
  24. parseErrorJson,
  25. ErrorTypeEnum,
  26. } from '@superset-ui/core';
  27. test('Returns a Promise', () => {
  28. const response = getClientErrorObject('error');
  29. expect(response instanceof Promise).toBe(true);
  30. });
  31. test('Returns a Promise that resolves to an object with an error key', async () => {
  32. const error = 'error';
  33. const errorObj = await getClientErrorObject(error);
  34. expect(errorObj).toMatchObject({ error });
  35. });
  36. test('should handle HTML response with "500" or "server error"', async () => {
  37. const htmlString500 = '<div>500: Internal Server Error</div>';
  38. const clientErrorObject500 = await getClientErrorObject(htmlString500);
  39. expect(clientErrorObject500).toEqual({ error: 'Server error' });
  40. const htmlStringServerError = '<div>Server error message</div>';
  41. const clientErrorObjectServerError = await getClientErrorObject(
  42. htmlStringServerError,
  43. );
  44. expect(clientErrorObjectServerError).toEqual({
  45. error: 'Server error',
  46. });
  47. });
  48. test('should handle HTML response with "404" or "not found"', async () => {
  49. const htmlString404 = '<div>404: Page not found</div>';
  50. const clientErrorObject404 = await getClientErrorObject(htmlString404);
  51. expect(clientErrorObject404).toEqual({ error: 'Not found' });
  52. const htmlStringNotFoundError = '<div>Not found message</div>';
  53. const clientErrorObjectNotFoundError = await getClientErrorObject(
  54. htmlStringNotFoundError,
  55. );
  56. expect(clientErrorObjectNotFoundError).toEqual({
  57. error: 'Not found',
  58. });
  59. });
  60. test('should handle HTML response without common error code', async () => {
  61. const htmlString = '<!doctype html><div>Foo bar Lorem Ipsum</div>';
  62. const clientErrorObject = await getClientErrorObject(htmlString);
  63. expect(clientErrorObject).toEqual({ error: 'Unknown error' });
  64. const htmlString2 = '<div><p>An error occurred</p></div>';
  65. const clientErrorObject2 = await getClientErrorObject(htmlString2);
  66. expect(clientErrorObject2).toEqual({
  67. error: 'Unknown error',
  68. });
  69. });
  70. test('Handles Response that can be parsed as json', async () => {
  71. const jsonError = { something: 'something', error: 'Error message' };
  72. const jsonErrorString = JSON.stringify(jsonError);
  73. const errorObj = await getClientErrorObject(new Response(jsonErrorString));
  74. expect(errorObj).toMatchObject(jsonError);
  75. });
  76. test('Handles backwards compatibility between old error messages and the new SIP-40 errors format', async () => {
  77. const jsonError = {
  78. errors: [
  79. {
  80. error_type: ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
  81. extra: { engine: 'presto', link: 'https://www.google.com' },
  82. level: 'error',
  83. message: 'presto error: test error',
  84. },
  85. ],
  86. };
  87. const jsonErrorString = JSON.stringify(jsonError);
  88. const errorObj = await getClientErrorObject(new Response(jsonErrorString));
  89. expect(errorObj.error).toEqual(jsonError.errors[0].message);
  90. expect(errorObj.link).toEqual(jsonError.errors[0].extra.link);
  91. });
  92. test('Handles Response that can be parsed as text', async () => {
  93. const textError = 'Hello I am a text error';
  94. const errorObj = await getClientErrorObject(new Response(textError));
  95. expect(errorObj).toMatchObject({ error: textError });
  96. });
  97. test('Handles Response that contains raw html be parsed as text', async () => {
  98. const textError = 'Hello I am a text error';
  99. const errorObj = await getClientErrorObject(new Response(textError));
  100. expect(errorObj).toMatchObject({ error: textError });
  101. });
  102. test('Handles TypeError Response', async () => {
  103. const error = new TypeError('Failed to fetch');
  104. // @ts-ignore
  105. const errorObj = await getClientErrorObject(error);
  106. expect(errorObj).toMatchObject({ error: 'Network error' });
  107. });
  108. test('Handles timeout error', async () => {
  109. const errorObj = await getClientErrorObject({
  110. timeout: 1000,
  111. statusText: 'timeout',
  112. });
  113. expect(errorObj).toMatchObject({
  114. timeout: 1000,
  115. statusText: 'timeout',
  116. error: 'Request timed out',
  117. errors: [
  118. {
  119. error_type: ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR,
  120. extra: {
  121. timeout: 1,
  122. issue_codes: [
  123. {
  124. code: 1000,
  125. message: 'Issue 1000 - The dataset is too large to query.',
  126. },
  127. {
  128. code: 1001,
  129. message: 'Issue 1001 - The database is under an unusual load.',
  130. },
  131. ],
  132. },
  133. level: 'error',
  134. message: 'Request timed out',
  135. },
  136. ],
  137. });
  138. });
  139. test('Handles plain text as input', async () => {
  140. const error = 'error';
  141. const errorObj = await getClientErrorObject(error);
  142. expect(errorObj).toMatchObject({ error });
  143. });
  144. test('Handles error with status code', async () => {
  145. const status500 = new Response(null, { status: 500 });
  146. const status404 = new Response(null, { status: 404 });
  147. const status502 = new Response(null, { status: 502 });
  148. expect(await getClientErrorObject(status500)).toMatchObject({
  149. error: 'Server error',
  150. });
  151. expect(await getClientErrorObject(status404)).toMatchObject({
  152. error: 'Not found',
  153. });
  154. expect(await getClientErrorObject(status502)).toMatchObject({
  155. error: 'Bad gateway',
  156. });
  157. });
  158. test('Handles error with status text and message', async () => {
  159. const statusText = 'status';
  160. const message = 'message';
  161. // @ts-ignore
  162. expect(await getClientErrorObject({ statusText, message })).toMatchObject({
  163. error: statusText,
  164. });
  165. // @ts-ignore
  166. expect(await getClientErrorObject({ message })).toMatchObject({
  167. error: message,
  168. });
  169. // @ts-ignore
  170. expect(await getClientErrorObject({})).toMatchObject({
  171. error: 'An error occurred',
  172. });
  173. });
  174. test('getClientErrorMessage', () => {
  175. expect(getClientErrorMessage('error')).toEqual('error');
  176. expect(
  177. getClientErrorMessage('error', {
  178. error: 'client error',
  179. message: 'client error message',
  180. }),
  181. ).toEqual('error:\nclient error message');
  182. expect(
  183. getClientErrorMessage('error', {
  184. error: 'client error',
  185. }),
  186. ).toEqual('error:\nclient error');
  187. });
  188. test('parseErrorJson with message', () => {
  189. expect(parseErrorJson({ message: 'error message' })).toEqual({
  190. message: 'error message',
  191. error: 'error message',
  192. });
  193. expect(
  194. parseErrorJson({
  195. message: {
  196. key1: ['error message1', 'error message2'],
  197. key2: ['error message3', 'error message4'],
  198. },
  199. }),
  200. ).toEqual({
  201. message: {
  202. key1: ['error message1', 'error message2'],
  203. key2: ['error message3', 'error message4'],
  204. },
  205. error: 'error message1',
  206. });
  207. expect(
  208. parseErrorJson({
  209. message: {},
  210. }),
  211. ).toEqual({
  212. message: {},
  213. error: 'Invalid input',
  214. });
  215. });
  216. test('parseErrorJson with HTML message', () => {
  217. expect(
  218. parseErrorJson({
  219. message: '<div>error message</div>',
  220. }),
  221. ).toEqual({
  222. message: '<div>error message</div>',
  223. error: 'Unknown error',
  224. });
  225. expect(
  226. parseErrorJson({
  227. message: '<div>Server error</div>',
  228. }),
  229. ).toEqual({
  230. message: '<div>Server error</div>',
  231. error: 'Server error',
  232. });
  233. });
  234. test('parseErrorJson with HTML message and status code', () => {
  235. expect(
  236. parseErrorJson({
  237. status: 502,
  238. message: '<div>error message</div>',
  239. }),
  240. ).toEqual({
  241. status: 502,
  242. message: '<div>error message</div>',
  243. error: 'Bad gateway',
  244. });
  245. expect(
  246. parseErrorJson({
  247. status: 999,
  248. message: '<div>Server error</div>',
  249. }),
  250. ).toEqual({
  251. status: 999,
  252. message: '<div>Server error</div>',
  253. error: 'Server error',
  254. });
  255. });
  256. test('parseErrorJson with stacktrace', () => {
  257. expect(
  258. parseErrorJson({ error: 'error message', stack: 'stacktrace' }),
  259. ).toEqual({
  260. error: 'Unexpected error: (no description, click to see stack trace)',
  261. stacktrace: 'stacktrace',
  262. stack: 'stacktrace',
  263. });
  264. expect(
  265. parseErrorJson({
  266. error: 'error message',
  267. description: 'error description',
  268. stack: 'stacktrace',
  269. }),
  270. ).toEqual({
  271. error: 'Unexpected error: error description',
  272. stacktrace: 'stacktrace',
  273. description: 'error description',
  274. stack: 'stacktrace',
  275. });
  276. });
  277. test('parseErrorJson with CSRF', () => {
  278. expect(
  279. parseErrorJson({
  280. responseText: 'CSRF',
  281. }),
  282. ).toEqual({
  283. error: COMMON_ERR_MESSAGES.SESSION_TIMED_OUT,
  284. responseText: 'CSRF',
  285. });
  286. });
  287. test('getErrorText', async () => {
  288. expect(await getErrorText('error', 'dashboard')).toEqual(
  289. 'Sorry, there was an error saving this dashboard: error',
  290. );
  291. const error = JSON.stringify({ message: 'Forbidden' });
  292. expect(await getErrorText(new Response(error), 'dashboard')).toEqual(
  293. 'You do not have permission to edit this dashboard',
  294. );
  295. expect(
  296. await getErrorText(
  297. new Response(JSON.stringify({ status: 'error' })),
  298. 'dashboard',
  299. ),
  300. ).toEqual('Sorry, an unknown error occurred.');
  301. });