SupersetClientClass.test.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  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 fetchMock from 'fetch-mock';
  20. import { SupersetClientClass, ClientConfig, CallApi } from '@superset-ui/core';
  21. import { LOGIN_GLOB } from './fixtures/constants';
  22. describe('SupersetClientClass', () => {
  23. beforeEach(() => {
  24. fetchMock.reset();
  25. fetchMock.get(LOGIN_GLOB, { result: '' });
  26. });
  27. afterAll(() => fetchMock.restore());
  28. describe('new SupersetClientClass()', () => {
  29. it('fallback protocol to https when setting only host', () => {
  30. const client = new SupersetClientClass({ host: 'TEST-HOST' });
  31. expect(client.protocol).toEqual('https:');
  32. expect(client.host).toEqual('test-host');
  33. });
  34. });
  35. describe('.getUrl()', () => {
  36. let client = new SupersetClientClass();
  37. beforeEach(() => {
  38. client = new SupersetClientClass({
  39. protocol: 'https:',
  40. host: 'CONFIG_HOST',
  41. });
  42. });
  43. it('uses url if passed', () => {
  44. expect(
  45. client.getUrl({ url: 'myUrl', endpoint: 'blah', host: 'blah' }),
  46. ).toBe('myUrl');
  47. });
  48. it('constructs a valid url from config.protocol + host + endpoint if passed', () => {
  49. expect(client.getUrl({ endpoint: '/test', host: 'myhost' })).toBe(
  50. 'https://myhost/test',
  51. );
  52. expect(client.getUrl({ endpoint: '/test', host: 'myhost/' })).toBe(
  53. 'https://myhost/test',
  54. );
  55. expect(client.getUrl({ endpoint: 'test', host: 'myhost' })).toBe(
  56. 'https://myhost/test',
  57. );
  58. expect(client.getUrl({ endpoint: '/test/test//', host: 'myhost/' })).toBe(
  59. 'https://myhost/test/test//',
  60. );
  61. });
  62. it('constructs a valid url from config.host + endpoint if host is omitted', () => {
  63. expect(client.getUrl({ endpoint: '/test' })).toBe(
  64. 'https://config_host/test',
  65. );
  66. });
  67. it('constructs a valid url if url, endpoint, and host are all empty and appRoot is defined', () => {
  68. client = new SupersetClientClass({
  69. protocol: 'https:',
  70. host: 'config_host',
  71. appRoot: '/prefix',
  72. });
  73. expect(client.getUrl()).toBe('https://config_host/prefix/');
  74. });
  75. it('does not throw if url, endpoint, and host are all empty', () => {
  76. client = new SupersetClientClass({ protocol: 'https:', host: '' });
  77. expect(client.getUrl()).toBe('https://localhost/');
  78. });
  79. });
  80. describe('.init()', () => {
  81. beforeEach(() =>
  82. fetchMock.get(LOGIN_GLOB, { result: 1234 }, { overwriteRoutes: true }),
  83. );
  84. afterEach(() => fetchMock.reset());
  85. it('calls api/v1/security/csrf_token/ when init() is called if no CSRF token is passed', async () => {
  86. expect.assertions(1);
  87. await new SupersetClientClass().init();
  88. expect(fetchMock.calls(LOGIN_GLOB)).toHaveLength(1);
  89. });
  90. it('does NOT call api/v1/security/csrf_token/ when init() is called if a CSRF token is passed', async () => {
  91. expect.assertions(1);
  92. await new SupersetClientClass({ csrfToken: 'abc' }).init();
  93. expect(fetchMock.calls(LOGIN_GLOB)).toHaveLength(0);
  94. });
  95. it('calls api/v1/security/csrf_token/ when init(force=true) is called even if a CSRF token is passed', async () => {
  96. expect.assertions(4);
  97. const initialToken = 'initial_token';
  98. const client = new SupersetClientClass({ csrfToken: initialToken });
  99. await client.init();
  100. expect(fetchMock.calls(LOGIN_GLOB)).toHaveLength(0);
  101. expect(client.csrfToken).toBe(initialToken);
  102. await client.init(true);
  103. expect(fetchMock.calls(LOGIN_GLOB)).toHaveLength(1);
  104. expect(client.csrfToken).not.toBe(initialToken);
  105. });
  106. it('throws if api/v1/security/csrf_token/ returns an error', async () => {
  107. expect.assertions(1);
  108. const rejectError = { status: 403 };
  109. fetchMock.get(LOGIN_GLOB, () => Promise.reject(rejectError), {
  110. overwriteRoutes: true,
  111. });
  112. let error;
  113. try {
  114. await new SupersetClientClass({}).init();
  115. } catch (err) {
  116. error = err;
  117. } finally {
  118. expect(error as typeof rejectError).toEqual(rejectError);
  119. }
  120. });
  121. const invalidCsrfTokenError = { error: 'Failed to fetch CSRF token' };
  122. it('throws if api/v1/security/csrf_token/ does not return a token', async () => {
  123. expect.assertions(1);
  124. fetchMock.get(LOGIN_GLOB, {}, { overwriteRoutes: true });
  125. let error;
  126. try {
  127. await new SupersetClientClass({}).init();
  128. } catch (err) {
  129. error = err;
  130. } finally {
  131. expect(error as typeof invalidCsrfTokenError).toEqual(
  132. invalidCsrfTokenError,
  133. );
  134. }
  135. });
  136. it('does not set csrfToken if response is not json', async () => {
  137. expect.assertions(1);
  138. fetchMock.get(LOGIN_GLOB, '123', {
  139. overwriteRoutes: true,
  140. });
  141. let error;
  142. try {
  143. await new SupersetClientClass({}).init();
  144. } catch (err) {
  145. error = err;
  146. } finally {
  147. expect(error as typeof invalidCsrfTokenError).toEqual(
  148. invalidCsrfTokenError,
  149. );
  150. }
  151. });
  152. });
  153. describe('.isAuthenticated()', () => {
  154. afterEach(() => fetchMock.reset());
  155. it('returns true if there is a token and false if not', async () => {
  156. expect.assertions(2);
  157. const client = new SupersetClientClass({});
  158. expect(client.isAuthenticated()).toBe(false);
  159. await client.init();
  160. expect(client.isAuthenticated()).toBe(true);
  161. });
  162. it('returns true if a token is passed at configuration', () => {
  163. expect.assertions(2);
  164. const clientWithoutToken = new SupersetClientClass({
  165. csrfToken: undefined,
  166. });
  167. const clientWithToken = new SupersetClientClass({ csrfToken: 'token' });
  168. expect(clientWithoutToken.isAuthenticated()).toBe(false);
  169. expect(clientWithToken.isAuthenticated()).toBe(true);
  170. });
  171. });
  172. describe('.ensureAuth()', () => {
  173. it(`returns a promise that rejects if .init() has not been called`, async () => {
  174. expect.assertions(2);
  175. const client = new SupersetClientClass({});
  176. let error;
  177. try {
  178. await client.ensureAuth();
  179. } catch (err) {
  180. error = err;
  181. } finally {
  182. expect(error).toEqual({ error: expect.any(String) });
  183. }
  184. expect(client.isAuthenticated()).toBe(false);
  185. });
  186. it('returns a promise that resolves if .init() resolves successfully', async () => {
  187. expect.assertions(1);
  188. const client = new SupersetClientClass({});
  189. await client.init();
  190. await client.ensureAuth();
  191. expect(client.isAuthenticated()).toBe(true);
  192. });
  193. it(`returns a promise that rejects if .init() is unsuccessful`, async () => {
  194. expect.assertions(4);
  195. const rejectValue = { status: 403 };
  196. fetchMock.get(LOGIN_GLOB, () => Promise.reject(rejectValue), {
  197. overwriteRoutes: true,
  198. });
  199. const client = new SupersetClientClass({});
  200. let error;
  201. let error2;
  202. try {
  203. await client.init();
  204. } catch (err) {
  205. error = err;
  206. } finally {
  207. expect(error).toEqual(expect.objectContaining(rejectValue));
  208. expect(client.isAuthenticated()).toBe(false);
  209. try {
  210. await client.ensureAuth();
  211. } catch (err) {
  212. error2 = err;
  213. } finally {
  214. expect(error2).toEqual(expect.objectContaining(rejectValue));
  215. expect(client.isAuthenticated()).toBe(false);
  216. }
  217. }
  218. // reset
  219. fetchMock.get(
  220. LOGIN_GLOB,
  221. { result: 1234 },
  222. {
  223. overwriteRoutes: true,
  224. },
  225. );
  226. });
  227. });
  228. describe('requests', () => {
  229. afterEach(() => fetchMock.restore());
  230. const protocol = 'https:';
  231. const host = 'host';
  232. const mockGetEndpoint = '/get/url';
  233. const mockRequestEndpoint = '/request/url';
  234. const mockPostEndpoint = '/post/url';
  235. const mockPutEndpoint = '/put/url';
  236. const mockDeleteEndpoint = '/delete/url';
  237. const mockTextEndpoint = '/text/endpoint';
  238. const mockGetUrl = `${protocol}//${host}${mockGetEndpoint}`;
  239. const mockRequestUrl = `${protocol}//${host}${mockRequestEndpoint}`;
  240. const mockPostUrl = `${protocol}//${host}${mockPostEndpoint}`;
  241. const mockTextUrl = `${protocol}//${host}${mockTextEndpoint}`;
  242. const mockPutUrl = `${protocol}//${host}${mockPutEndpoint}`;
  243. const mockDeleteUrl = `${protocol}//${host}${mockDeleteEndpoint}`;
  244. const mockTextJsonResponse = '{ "value": 9223372036854775807 }';
  245. const mockPayload = { json: () => Promise.resolve('payload') };
  246. beforeEach(() => {
  247. fetchMock.get(mockGetUrl, mockPayload);
  248. fetchMock.post(mockPostUrl, mockPayload);
  249. fetchMock.put(mockPutUrl, mockPayload);
  250. fetchMock.delete(mockDeleteUrl, mockPayload);
  251. fetchMock.delete(mockRequestUrl, mockPayload);
  252. fetchMock.get(mockTextUrl, mockTextJsonResponse);
  253. fetchMock.post(mockTextUrl, mockTextJsonResponse);
  254. });
  255. it('checks for authentication before every get and post request', async () => {
  256. expect.assertions(6);
  257. const authSpy = jest.spyOn(SupersetClientClass.prototype, 'ensureAuth');
  258. const client = new SupersetClientClass({ protocol, host });
  259. await client.init();
  260. await client.get({ url: mockGetUrl });
  261. await client.post({ url: mockPostUrl });
  262. await client.put({ url: mockPutUrl });
  263. await client.delete({ url: mockDeleteUrl });
  264. await client.request({ url: mockRequestUrl, method: 'DELETE' });
  265. expect(fetchMock.calls(mockGetUrl)).toHaveLength(1);
  266. expect(fetchMock.calls(mockPostUrl)).toHaveLength(1);
  267. expect(fetchMock.calls(mockDeleteUrl)).toHaveLength(1);
  268. expect(fetchMock.calls(mockPutUrl)).toHaveLength(1);
  269. expect(fetchMock.calls(mockRequestUrl)).toHaveLength(1);
  270. expect(authSpy).toHaveBeenCalledTimes(5);
  271. authSpy.mockRestore();
  272. });
  273. it('sets protocol, host, headers, mode, and credentials from config', async () => {
  274. expect.assertions(3);
  275. const clientConfig: ClientConfig = {
  276. host,
  277. protocol,
  278. mode: 'cors',
  279. credentials: 'include',
  280. headers: { my: 'header' },
  281. };
  282. const client = new SupersetClientClass(clientConfig);
  283. await client.init();
  284. await client.get({ url: mockGetUrl });
  285. const fetchRequest = fetchMock.calls(mockGetUrl)[0][1] as CallApi;
  286. expect(fetchRequest.mode).toBe(clientConfig.mode);
  287. expect(fetchRequest.credentials).toBe(clientConfig.credentials);
  288. expect(fetchRequest.headers).toEqual(
  289. expect.objectContaining(
  290. clientConfig.headers,
  291. ) as typeof fetchRequest.headers,
  292. );
  293. });
  294. it('uses a guest token when provided', async () => {
  295. expect.assertions(2);
  296. const client = new SupersetClientClass({
  297. protocol,
  298. host,
  299. guestToken: 'abc123',
  300. guestTokenHeaderName: 'guestTokenHeader',
  301. });
  302. expect(client.getGuestToken()).toBe('abc123');
  303. await client.init();
  304. await client.get({ url: mockGetUrl });
  305. const fetchRequest = fetchMock.calls(mockGetUrl)[0][1] as CallApi;
  306. expect(fetchRequest.headers).toEqual(
  307. expect.objectContaining({
  308. guestTokenHeader: 'abc123',
  309. }),
  310. );
  311. });
  312. describe('.get()', () => {
  313. it('makes a request using url or endpoint', async () => {
  314. expect.assertions(2);
  315. const client = new SupersetClientClass({ protocol, host });
  316. await client.init();
  317. await client.get({ url: mockGetUrl });
  318. expect(fetchMock.calls(mockGetUrl)).toHaveLength(1);
  319. await client.get({ endpoint: mockGetEndpoint });
  320. expect(fetchMock.calls(mockGetUrl)).toHaveLength(2);
  321. });
  322. it('supports parsing a response as text', async () => {
  323. expect.assertions(2);
  324. const client = new SupersetClientClass({ protocol, host });
  325. await client.init();
  326. const { text } = await client.get({
  327. url: mockTextUrl,
  328. parseMethod: 'text',
  329. });
  330. expect(fetchMock.calls(mockTextUrl)).toHaveLength(1);
  331. expect(text).toBe(mockTextJsonResponse);
  332. });
  333. it('allows overriding host, headers, mode, and credentials per-request', async () => {
  334. expect.assertions(3);
  335. const clientConfig: ClientConfig = {
  336. host,
  337. protocol,
  338. mode: 'cors',
  339. credentials: 'include',
  340. headers: { my: 'header' },
  341. };
  342. const overrideConfig: ClientConfig = {
  343. host: 'override_host',
  344. mode: 'no-cors',
  345. credentials: 'omit',
  346. headers: { my: 'override', another: 'header' },
  347. };
  348. const client = new SupersetClientClass(clientConfig);
  349. await client.init();
  350. await client.get({ url: mockGetUrl, ...overrideConfig });
  351. const fetchRequest = fetchMock.calls(mockGetUrl)[0][1] as CallApi;
  352. expect(fetchRequest.mode).toBe(overrideConfig.mode);
  353. expect(fetchRequest.credentials).toBe(overrideConfig.credentials);
  354. expect(fetchRequest.headers).toEqual(
  355. expect.objectContaining(
  356. overrideConfig.headers,
  357. ) as typeof fetchRequest.headers,
  358. );
  359. });
  360. });
  361. describe('.post()', () => {
  362. it('makes a request using url or endpoint', async () => {
  363. expect.assertions(2);
  364. const client = new SupersetClientClass({ protocol, host });
  365. await client.init();
  366. await client.post({ url: mockPostUrl });
  367. expect(fetchMock.calls(mockPostUrl)).toHaveLength(1);
  368. await client.post({ endpoint: mockPostEndpoint });
  369. expect(fetchMock.calls(mockPostUrl)).toHaveLength(2);
  370. });
  371. it('allows overriding host, headers, mode, and credentials per-request', async () => {
  372. expect.assertions(3);
  373. const clientConfig: ClientConfig = {
  374. host,
  375. protocol,
  376. mode: 'cors',
  377. credentials: 'include',
  378. headers: { my: 'header' },
  379. };
  380. const overrideConfig: ClientConfig = {
  381. host: 'override_host',
  382. mode: 'no-cors',
  383. credentials: 'omit',
  384. headers: { my: 'override', another: 'header' },
  385. };
  386. const client = new SupersetClientClass(clientConfig);
  387. await client.init();
  388. await client.post({ url: mockPostUrl, ...overrideConfig });
  389. const fetchRequest = fetchMock.calls(mockPostUrl)[0][1] as CallApi;
  390. expect(fetchRequest.mode).toBe(overrideConfig.mode);
  391. expect(fetchRequest.credentials).toBe(overrideConfig.credentials);
  392. expect(fetchRequest.headers).toEqual(
  393. expect.objectContaining(
  394. overrideConfig.headers,
  395. ) as typeof fetchRequest.headers,
  396. );
  397. });
  398. it('supports parsing a response as text', async () => {
  399. expect.assertions(2);
  400. const client = new SupersetClientClass({ protocol, host });
  401. await client.init();
  402. const { text } = await client.post({
  403. url: mockTextUrl,
  404. parseMethod: 'text',
  405. });
  406. expect(fetchMock.calls(mockTextUrl)).toHaveLength(1);
  407. expect(text).toBe(mockTextJsonResponse);
  408. });
  409. it('passes postPayload key,values in the body', async () => {
  410. expect.assertions(3);
  411. const postPayload = { number: 123, array: [1, 2, 3] };
  412. const client = new SupersetClientClass({ protocol, host });
  413. await client.init();
  414. await client.post({ url: mockPostUrl, postPayload });
  415. const fetchRequest = fetchMock.calls(mockPostUrl)[0][1] as CallApi;
  416. const formData = fetchRequest.body as FormData;
  417. expect(fetchMock.calls(mockPostUrl)).toHaveLength(1);
  418. Object.entries(postPayload).forEach(([key, value]) => {
  419. expect(formData.get(key)).toBe(JSON.stringify(value));
  420. });
  421. });
  422. it('respects the stringify parameter for postPayload key,values', async () => {
  423. expect.assertions(3);
  424. const postPayload = { number: 123, array: [1, 2, 3] };
  425. const client = new SupersetClientClass({ protocol, host });
  426. await client.init();
  427. await client.post({ url: mockPostUrl, postPayload, stringify: false });
  428. const fetchRequest = fetchMock.calls(mockPostUrl)[0][1] as CallApi;
  429. const formData = fetchRequest.body as FormData;
  430. expect(fetchMock.calls(mockPostUrl)).toHaveLength(1);
  431. Object.entries(postPayload).forEach(([key, value]) => {
  432. expect(formData.get(key)).toBe(String(value));
  433. });
  434. });
  435. });
  436. });
  437. describe('when unauthorized', () => {
  438. let originalLocation: any;
  439. let authSpy: jest.SpyInstance;
  440. const mockRequestUrl = 'https://host/get/url';
  441. const mockRequestPath = '/get/url';
  442. const mockRequestSearch = '?param=1&param=2';
  443. const mockHref = mockRequestUrl + mockRequestSearch;
  444. beforeEach(() => {
  445. originalLocation = window.location;
  446. // @ts-ignore
  447. delete window.location;
  448. // @ts-ignore
  449. window.location = {
  450. pathname: mockRequestPath,
  451. search: mockRequestSearch,
  452. href: mockHref,
  453. };
  454. authSpy = jest
  455. .spyOn(SupersetClientClass.prototype, 'ensureAuth')
  456. .mockImplementation();
  457. const rejectValue = { status: 401 };
  458. fetchMock.get(mockRequestUrl, () => Promise.reject(rejectValue), {
  459. overwriteRoutes: true,
  460. });
  461. });
  462. afterEach(() => {
  463. authSpy.mockReset();
  464. window.location = originalLocation;
  465. });
  466. it('should redirect', async () => {
  467. const client = new SupersetClientClass({});
  468. let error;
  469. try {
  470. await client.request({ url: mockRequestUrl, method: 'GET' });
  471. } catch (err) {
  472. error = err;
  473. } finally {
  474. const redirectURL = window.location.href;
  475. expect(redirectURL).toBe(`/login?next=${mockHref}`);
  476. expect(error.status).toBe(401);
  477. }
  478. });
  479. it('should not redirect again if already on login page', async () => {
  480. const client = new SupersetClientClass({});
  481. // @ts-expect-error
  482. window.location = {
  483. href: '/login?next=something',
  484. pathname: '/login',
  485. search: '?next=something',
  486. };
  487. let error;
  488. try {
  489. await client.request({ url: mockRequestUrl, method: 'GET' });
  490. } catch (err) {
  491. error = err;
  492. } finally {
  493. expect(window.location.href).toBe('/login?next=something');
  494. expect(error.status).toBe(401);
  495. }
  496. });
  497. it('does nothing if instructed to ignoreUnauthorized', async () => {
  498. const client = new SupersetClientClass({});
  499. let error;
  500. try {
  501. await client.request({
  502. url: mockRequestUrl,
  503. method: 'GET',
  504. ignoreUnauthorized: true,
  505. });
  506. } catch (err) {
  507. error = err;
  508. } finally {
  509. // unchanged href, no redirect
  510. expect(window.location.href).toBe(mockHref);
  511. expect(error.status).toBe(401);
  512. }
  513. });
  514. it('accepts an unauthorizedHandler to override redirect behavior', async () => {
  515. const unauthorizedHandler = jest.fn();
  516. const client = new SupersetClientClass({ unauthorizedHandler });
  517. let error;
  518. try {
  519. await client.request({
  520. url: mockRequestUrl,
  521. method: 'GET',
  522. });
  523. } catch (err) {
  524. error = err;
  525. } finally {
  526. // unchanged href, no redirect
  527. expect(window.location.href).toBe(mockHref);
  528. expect(error.status).toBe(401);
  529. expect(unauthorizedHandler).toHaveBeenCalledTimes(1);
  530. }
  531. });
  532. });
  533. describe('.postForm()', () => {
  534. const protocol = 'https:';
  535. const host = 'host';
  536. const mockPostFormEndpoint = '/post_form/url';
  537. const mockPostFormUrl = `${protocol}//${host}${mockPostFormEndpoint}`;
  538. const guestToken = 'test-guest-token';
  539. const postFormPayload = { number: 123, array: [1, 2, 3] };
  540. let authSpy: jest.SpyInstance;
  541. let client: SupersetClientClass;
  542. let appendChild: any;
  543. let removeChild: any;
  544. let submit: any;
  545. let createElement: any;
  546. beforeEach(async () => {
  547. fetchMock.get(LOGIN_GLOB, { result: 1234 }, { overwriteRoutes: true });
  548. client = new SupersetClientClass({ protocol, host });
  549. authSpy = jest.spyOn(SupersetClientClass.prototype, 'ensureAuth');
  550. await client.init();
  551. appendChild = jest.fn();
  552. removeChild = jest.fn();
  553. submit = jest.fn();
  554. createElement = jest.fn(() => ({
  555. appendChild: jest.fn(),
  556. submit,
  557. }));
  558. document.createElement = createElement as any;
  559. document.body.appendChild = appendChild;
  560. document.body.removeChild = removeChild;
  561. });
  562. afterEach(() => {
  563. jest.restoreAllMocks();
  564. });
  565. it.each(['', '/prefix'])(
  566. "makes postForm request when appRoot is '%s'",
  567. async appRoot => {
  568. if (appRoot !== '') {
  569. client = new SupersetClientClass({ protocol, host, appRoot });
  570. authSpy = jest.spyOn(SupersetClientClass.prototype, 'ensureAuth');
  571. await client.init();
  572. }
  573. await client.postForm(mockPostFormEndpoint, {});
  574. const hiddenForm = createElement.mock.results[0].value;
  575. const csrfTokenInput = createElement.mock.results[1].value;
  576. expect(createElement.mock.calls).toHaveLength(2);
  577. expect(hiddenForm.action).toBe(
  578. `${protocol}//${host}${appRoot}${mockPostFormEndpoint}`,
  579. );
  580. expect(hiddenForm.method).toBe('POST');
  581. expect(hiddenForm.target).toBe('_blank');
  582. expect(csrfTokenInput.type).toBe('hidden');
  583. expect(csrfTokenInput.name).toBe('csrf_token');
  584. expect(csrfTokenInput.value).toBe(1234);
  585. expect(appendChild.mock.calls).toHaveLength(1);
  586. expect(removeChild.mock.calls).toHaveLength(1);
  587. expect(authSpy).toHaveBeenCalledTimes(1);
  588. },
  589. );
  590. it('makes postForm request with guest token', async () => {
  591. client = new SupersetClientClass({ protocol, host, guestToken });
  592. await client.init();
  593. await client.postForm(mockPostFormUrl, {});
  594. const guestTokenInput = createElement.mock.results[2].value;
  595. expect(createElement.mock.calls).toHaveLength(3);
  596. expect(guestTokenInput.type).toBe('hidden');
  597. expect(guestTokenInput.name).toBe('guest_token');
  598. expect(guestTokenInput.value).toBe(guestToken);
  599. expect(appendChild.mock.calls).toHaveLength(1);
  600. expect(removeChild.mock.calls).toHaveLength(1);
  601. expect(authSpy).toHaveBeenCalledTimes(1);
  602. });
  603. it('makes postForm request with payload', async () => {
  604. await client.postForm(mockPostFormUrl, { form_data: postFormPayload });
  605. const postFormPayloadInput = createElement.mock.results[1].value;
  606. expect(createElement.mock.calls).toHaveLength(3);
  607. expect(postFormPayloadInput.type).toBe('hidden');
  608. expect(postFormPayloadInput.name).toBe('form_data');
  609. expect(postFormPayloadInput.value).toBe(postFormPayload);
  610. expect(appendChild.mock.calls).toHaveLength(1);
  611. expect(removeChild.mock.calls).toHaveLength(1);
  612. expect(submit.mock.calls).toHaveLength(1);
  613. expect(authSpy).toHaveBeenCalledTimes(1);
  614. });
  615. it('should do nothing when url is empty string', async () => {
  616. const result = await client.postForm('', {});
  617. expect(result).toBeUndefined();
  618. expect(createElement.mock.calls).toHaveLength(0);
  619. expect(appendChild.mock.calls).toHaveLength(0);
  620. expect(removeChild.mock.calls).toHaveLength(0);
  621. expect(authSpy).toHaveBeenCalledTimes(0);
  622. });
  623. });
  624. });