relocation.spec.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. fireEvent,
  4. render,
  5. screen,
  6. userEvent,
  7. waitFor,
  8. } from 'sentry-test/reactTestingLibrary';
  9. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  10. import ConfigStore from 'sentry/stores/configStore';
  11. import type {InjectedRouter} from 'sentry/types/legacyReactRouter';
  12. import Relocation from 'sentry/views/relocation/relocation';
  13. jest.mock('sentry/actionCreators/indicator');
  14. const fakeOrgSlug = 'test-org';
  15. const fakePromoCode = 'free-hugs';
  16. const fakePublicKey = `FAKE-PK-ANY`;
  17. type FakeRegion = {
  18. name: string;
  19. publicKey: string;
  20. url: string;
  21. };
  22. const fakeRegions: {[key: string]: FakeRegion} = {
  23. Earth: {
  24. name: 'earth',
  25. url: 'https://earth.example.com',
  26. publicKey: 'FAKE-PK-EARTH',
  27. },
  28. Moon: {
  29. name: 'moon',
  30. url: 'https://moon.example.com',
  31. publicKey: 'FAKE-PK-MOON',
  32. },
  33. };
  34. describe('Relocation', function () {
  35. let router: InjectedRouter;
  36. let fetchExistingRelocations: jest.Mock;
  37. let fetchPublicKeys: jest.Mock;
  38. beforeEach(function () {
  39. MockApiClient.clearMockResponses();
  40. MockApiClient.asyncDelay = undefined;
  41. sessionStorage.clear();
  42. ConfigStore.set('regions', [
  43. {name: fakeRegions.Earth!.name, url: fakeRegions.Earth!.url},
  44. {name: fakeRegions.Moon!.name, url: fakeRegions.Moon!.url},
  45. ]);
  46. ConfigStore.set('relocationConfig', {
  47. selectableRegions: [fakeRegions.Earth!.name, fakeRegions.Moon!.name],
  48. });
  49. // For tests that don't care about the difference between our "earth" and "moon" regions, we can
  50. // re-use the same mock responses, with the same generic public key for both.
  51. fetchExistingRelocations = MockApiClient.addMockResponse({
  52. url: '/relocations/',
  53. body: [],
  54. });
  55. fetchPublicKeys = MockApiClient.addMockResponse({
  56. url: '/publickeys/relocations/',
  57. body: {
  58. public_key: fakePublicKey,
  59. },
  60. });
  61. // The tests fail because we have a "component update was not wrapped in act" error. It should
  62. // be safe to ignore this error, but we should remove the mock once we move to react testing
  63. // library.
  64. //
  65. jest.spyOn(console, 'error').mockImplementation(jest.fn());
  66. });
  67. afterEach(function () {
  68. MockApiClient.clearMockResponses();
  69. MockApiClient.asyncDelay = undefined;
  70. sessionStorage.clear();
  71. });
  72. function renderPage(step: string) {
  73. const routeParams = {
  74. step,
  75. };
  76. const {routerProps, organization, ...rest} = initializeOrg({
  77. router: {
  78. params: routeParams,
  79. },
  80. });
  81. router = rest.router;
  82. return render(<Relocation {...routerProps} />, {
  83. router,
  84. organization,
  85. });
  86. }
  87. async function waitForRenderSuccess(step: string) {
  88. renderPage(step);
  89. await screen.findByTestId(step);
  90. }
  91. async function waitForRenderError(step: string) {
  92. renderPage(step);
  93. await screen.findByTestId('loading-error');
  94. }
  95. describe('Get Started', function () {
  96. it('renders', async function () {
  97. await waitForRenderSuccess('get-started');
  98. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  99. expect(
  100. await screen.findByText('Basic information needed to get started')
  101. ).toBeInTheDocument();
  102. expect(
  103. await screen.findByText('Organization slugs being relocated')
  104. ).toBeInTheDocument();
  105. expect(await screen.findByText('Choose a datacenter location')).toBeInTheDocument();
  106. });
  107. it('redirects to `in-progress` page if user already has active relocation', async function () {
  108. MockApiClient.clearMockResponses();
  109. fetchExistingRelocations = MockApiClient.addMockResponse({
  110. url: '/relocations/',
  111. body: [
  112. {
  113. uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
  114. status: 'IN_PROGRESS',
  115. },
  116. ],
  117. });
  118. fetchPublicKeys = MockApiClient.addMockResponse({
  119. url: '/publickeys/relocations/',
  120. body: {
  121. public_key: fakePublicKey,
  122. },
  123. });
  124. await waitForRenderSuccess('get-started');
  125. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  126. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  127. expect(router.push).toHaveBeenCalledWith('/relocation/in-progress/');
  128. });
  129. it('should prevent user from going to the next step if no org slugs or region are entered', async function () {
  130. await waitForRenderSuccess('get-started');
  131. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  132. expect(screen.getByRole('button', {name: 'Continue'})).toBeDisabled();
  133. });
  134. it('should be allowed to go to next step if org slug is entered, region is selected, and promo code is entered', async function () {
  135. await waitForRenderSuccess('get-started');
  136. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  137. const fetchPromoCode = MockApiClient.addMockResponse({
  138. url: `/promocodes-external/${fakePromoCode}`,
  139. method: 'GET',
  140. statusCode: 200,
  141. });
  142. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  143. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  144. await userEvent.click(screen.getByRole('menuitemradio'));
  145. expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
  146. await userEvent.click(screen.getByText('Got a promo code?', {exact: false}));
  147. await userEvent.type(screen.getByLabelText('promocode'), fakePromoCode);
  148. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  149. await waitFor(() => expect(fetchPromoCode).toHaveBeenCalledTimes(1));
  150. expect(addErrorMessage).not.toHaveBeenCalled();
  151. });
  152. it('should persist form data across reloads', async function () {
  153. sessionStorage.setItem(
  154. 'relocationOnboarding',
  155. JSON.stringify({
  156. orgSlugs: fakeOrgSlug,
  157. promoCode: fakePromoCode,
  158. regionUrl: fakeRegions.Earth!.url,
  159. })
  160. );
  161. await waitForRenderSuccess('get-started');
  162. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  163. expect(screen.getByLabelText('org-slugs')).toHaveValue(fakeOrgSlug);
  164. expect(screen.getByLabelText('promocode')).toHaveValue(fakePromoCode);
  165. });
  166. it('should not be allowed to go to next step if org slug is entered, region is selected, and promo code is invalid', async function () {
  167. await waitForRenderSuccess('get-started');
  168. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  169. const fetchPromoCode = MockApiClient.addMockResponse({
  170. url: `/promocodes-external/${fakePromoCode}`,
  171. method: 'GET',
  172. statusCode: 403,
  173. });
  174. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  175. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  176. await userEvent.click(screen.getByRole('menuitemradio'));
  177. expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
  178. await userEvent.click(screen.getByText('Got a promo code?', {exact: false}));
  179. await userEvent.type(screen.getByLabelText('promocode'), fakePromoCode);
  180. expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
  181. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  182. await waitFor(() => expect(fetchPromoCode).toHaveBeenCalledTimes(1));
  183. expect(addErrorMessage).toHaveBeenCalledWith(
  184. 'That promotional code has already been claimed, does not have enough remaining uses, is no longer valid, or never existed.'
  185. );
  186. });
  187. it('should show loading indicator and error message if existing relocation retrieval failed', async function () {
  188. MockApiClient.clearMockResponses();
  189. // Note: only one fails, but that is enough.
  190. const failingFetchExistingEarthRelocation = MockApiClient.addMockResponse({
  191. host: fakeRegions.Earth!.url,
  192. url: `/relocations/`,
  193. statusCode: 400,
  194. });
  195. const successfulFetchExistingMoonRelocation = MockApiClient.addMockResponse({
  196. host: fakeRegions.Moon!.url,
  197. url: '/relocations/',
  198. body: [],
  199. });
  200. fetchPublicKeys = MockApiClient.addMockResponse({
  201. url: '/publickeys/relocations/',
  202. body: {
  203. public_key: fakePublicKey,
  204. },
  205. });
  206. await waitForRenderError('get-started');
  207. await waitFor(() =>
  208. expect(failingFetchExistingEarthRelocation).toHaveBeenCalledTimes(1)
  209. );
  210. await waitFor(() =>
  211. expect(successfulFetchExistingMoonRelocation).toHaveBeenCalledTimes(1)
  212. );
  213. expect(fetchPublicKeys).toHaveBeenCalledTimes(2);
  214. expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
  215. expect(screen.queryByLabelText('org-slugs')).not.toBeInTheDocument();
  216. expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
  217. const successfulFetchExistingEarthRelocation = MockApiClient.addMockResponse({
  218. host: fakeRegions.Earth!.url,
  219. url: '/relocations/',
  220. body: [],
  221. });
  222. await userEvent.click(screen.getByRole('button', {name: 'Retry'}));
  223. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  224. await screen.findByTestId('get-started');
  225. await waitFor(() =>
  226. expect(successfulFetchExistingEarthRelocation).toHaveBeenCalledTimes(1)
  227. );
  228. await waitFor(() =>
  229. expect(successfulFetchExistingMoonRelocation).toHaveBeenCalledTimes(2)
  230. );
  231. expect(screen.getByLabelText('org-slugs')).toBeInTheDocument();
  232. expect(screen.getByRole('button', {name: 'Continue'})).toBeInTheDocument();
  233. });
  234. });
  235. describe('Public Key', function () {
  236. beforeEach(function () {
  237. sessionStorage.setItem(
  238. 'relocationOnboarding',
  239. JSON.stringify({
  240. orgSlugs: fakeOrgSlug,
  241. promoCode: fakePromoCode,
  242. regionUrl: fakeRegions.Earth!.url,
  243. })
  244. );
  245. });
  246. it('should show instructions if key retrieval was successful', async function () {
  247. await waitForRenderSuccess('public-key');
  248. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  249. expect(
  250. await screen.findByText("Save Sentry's public key to your machine")
  251. ).toBeInTheDocument();
  252. expect(screen.getByText('key.pub')).toBeInTheDocument();
  253. expect(screen.getByRole('button', {name: 'Continue'})).toBeInTheDocument();
  254. });
  255. it('should show loading indicator if key retrieval still in progress', function () {
  256. MockApiClient.asyncDelay = 1;
  257. renderPage('public-key');
  258. expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
  259. expect(screen.queryByText('key.pub')).not.toBeInTheDocument();
  260. });
  261. it('should show loading indicator and error message if key retrieval failed', async function () {
  262. MockApiClient.clearMockResponses();
  263. fetchExistingRelocations = MockApiClient.addMockResponse({
  264. url: '/relocations/',
  265. body: [],
  266. });
  267. // Note: only one fails, but that is enough.
  268. const failingFetchEarthPublicKey = MockApiClient.addMockResponse({
  269. host: fakeRegions.Earth!.url,
  270. url: `/publickeys/relocations/`,
  271. statusCode: 400,
  272. });
  273. const successfulFetchMoonPublicKey = MockApiClient.addMockResponse({
  274. host: fakeRegions.Moon!.url,
  275. url: '/publickeys/relocations/',
  276. body: {
  277. public_key: fakeRegions.Moon!.publicKey,
  278. },
  279. });
  280. await waitForRenderError('public-key');
  281. await waitFor(() => expect(failingFetchEarthPublicKey).toHaveBeenCalledTimes(1));
  282. await waitFor(() => expect(successfulFetchMoonPublicKey).toHaveBeenCalledTimes(1));
  283. expect(fetchExistingRelocations).toHaveBeenCalledTimes(2);
  284. expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
  285. expect(screen.queryByText('key.pub')).not.toBeInTheDocument();
  286. expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
  287. const successfulFetchEarthPublicKey = MockApiClient.addMockResponse({
  288. host: fakeRegions.Earth!.url,
  289. url: '/publickeys/relocations/',
  290. body: {
  291. public_key: fakeRegions.Earth!.publicKey,
  292. },
  293. });
  294. await userEvent.click(screen.getByRole('button', {name: 'Retry'}));
  295. await waitFor(() => expect(successfulFetchEarthPublicKey).toHaveBeenCalledTimes(1));
  296. await waitFor(() => expect(successfulFetchMoonPublicKey).toHaveBeenCalledTimes(2));
  297. await screen.findByTestId('public-key');
  298. expect(fetchExistingRelocations).toHaveBeenCalledTimes(2);
  299. expect(screen.getByText('key.pub')).toBeInTheDocument();
  300. expect(screen.getByRole('button', {name: 'Continue'})).toBeInTheDocument();
  301. });
  302. it('redirects to `get-started` page if expected local storage data is missing', async function () {
  303. sessionStorage.setItem(
  304. 'relocationOnboarding',
  305. JSON.stringify({
  306. orgSlugs: fakeOrgSlug,
  307. // regionUrl missing
  308. })
  309. );
  310. await waitForRenderSuccess('public-key');
  311. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  312. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  313. expect(router.push).toHaveBeenCalledWith('/relocation/get-started/');
  314. });
  315. });
  316. describe('Encrypt Backup', function () {
  317. it('renders', async function () {
  318. await waitForRenderSuccess('encrypt-backup');
  319. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  320. expect(
  321. await screen.findByText(
  322. 'Create an encrypted backup of your current self-hosted instance'
  323. )
  324. ).toBeInTheDocument();
  325. });
  326. it('redirects to `get-started` page if expected local storage data is missing', async function () {
  327. sessionStorage.setItem(
  328. 'relocationOnboarding',
  329. JSON.stringify({
  330. // orgSlugs missing
  331. regionUrl: fakeRegions.Earth!.url,
  332. })
  333. );
  334. await waitForRenderSuccess('encrypt-backup');
  335. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  336. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  337. expect(router.push).toHaveBeenCalledWith('/relocation/get-started/');
  338. });
  339. });
  340. describe('Upload Backup', function () {
  341. beforeEach(function () {
  342. sessionStorage.setItem(
  343. 'relocationOnboarding',
  344. JSON.stringify({
  345. orgSlugs: fakeOrgSlug,
  346. promoCode: fakePromoCode,
  347. regionUrl: fakeRegions.Earth!.url,
  348. })
  349. );
  350. });
  351. it('renders', async function () {
  352. await waitForRenderSuccess('upload-backup');
  353. expect(
  354. await screen.findByText('Upload Tarball to begin the relocation process')
  355. ).toBeInTheDocument();
  356. });
  357. it('accepts a file upload', async function () {
  358. await waitForRenderSuccess('upload-backup');
  359. await userEvent.upload(
  360. screen.getByLabelText('file-upload'),
  361. new File(['hello'], 'hello.tar', {type: 'file'})
  362. );
  363. expect(await screen.findByText('hello.tar')).toBeInTheDocument();
  364. expect(await screen.findByText('Start Relocation')).toBeInTheDocument();
  365. });
  366. it('accepts a file upload through drag and drop', async function () {
  367. await waitForRenderSuccess('upload-backup');
  368. fireEvent.drop(screen.getByLabelText('dropzone'), {
  369. dataTransfer: {files: [new File(['hello'], 'hello.tar', {type: 'file'})]},
  370. });
  371. expect(await screen.findByText('hello.tar')).toBeInTheDocument();
  372. expect(await screen.findByText('Start Relocation')).toBeInTheDocument();
  373. });
  374. it('correctly removes file and prompts for file upload', async function () {
  375. await waitForRenderSuccess('upload-backup');
  376. await userEvent.upload(
  377. screen.getByLabelText('file-upload'),
  378. new File(['hello'], 'hello.tar', {type: 'file'})
  379. );
  380. await userEvent.click(screen.getByText('Remove file'));
  381. expect(screen.queryByText('hello.tar')).not.toBeInTheDocument();
  382. expect(
  383. await screen.findByText('Upload Tarball to begin the relocation process')
  384. ).toBeInTheDocument();
  385. });
  386. it('starts relocation job if form data is available from previous steps', async function () {
  387. const postRelocation = MockApiClient.addMockResponse({
  388. url: `/relocations/`,
  389. method: 'POST',
  390. responseJSON: [
  391. {
  392. uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
  393. status: 'IN_PROGRESS',
  394. },
  395. ],
  396. });
  397. await waitForRenderSuccess('get-started');
  398. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  399. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  400. await userEvent.click(screen.getByRole('menuitemradio'));
  401. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  402. await waitForRenderSuccess('upload-backup');
  403. await userEvent.upload(
  404. screen.getByLabelText('file-upload'),
  405. new File(['hello'], 'hello.tar', {type: 'file'})
  406. );
  407. await userEvent.click(await screen.findByText('Start Relocation'));
  408. await waitFor(() =>
  409. expect(postRelocation).toHaveBeenCalledWith(
  410. '/relocations/',
  411. expect.objectContaining({host: fakeRegions.Earth!.url, method: 'POST'})
  412. )
  413. );
  414. expect(addSuccessMessage).toHaveBeenCalledWith(
  415. "Your relocation has started - we'll email you with updates as soon as we have 'em!"
  416. );
  417. await waitForRenderSuccess('in-progress');
  418. });
  419. it('throws error if user already has an in-progress relocation job', async function () {
  420. const postRelocation = MockApiClient.addMockResponse({
  421. url: `/relocations/`,
  422. method: 'POST',
  423. statusCode: 409,
  424. });
  425. await waitForRenderSuccess('get-started');
  426. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  427. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  428. await userEvent.click(screen.getByRole('menuitemradio'));
  429. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  430. await waitForRenderSuccess('upload-backup');
  431. await userEvent.upload(
  432. screen.getByLabelText('file-upload'),
  433. new File(['hello'], 'hello.tar', {type: 'file'})
  434. );
  435. await userEvent.click(await screen.findByText('Start Relocation'));
  436. await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
  437. expect(addErrorMessage).toHaveBeenCalledWith(
  438. 'You already have an in-progress relocation job.'
  439. );
  440. });
  441. it('throws error if daily limit of relocations has been reached', async function () {
  442. const postRelocation = MockApiClient.addMockResponse({
  443. url: `/relocations/`,
  444. method: 'POST',
  445. statusCode: 429,
  446. });
  447. await waitForRenderSuccess('get-started');
  448. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  449. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  450. await userEvent.click(screen.getByRole('menuitemradio'));
  451. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  452. await waitForRenderSuccess('upload-backup');
  453. await userEvent.upload(
  454. screen.getByLabelText('file-upload'),
  455. new File(['hello'], 'hello.tar', {type: 'file'})
  456. );
  457. await userEvent.click(await screen.findByText('Start Relocation'));
  458. await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
  459. expect(addErrorMessage).toHaveBeenCalledWith(
  460. 'We have reached the daily limit of relocations - please try again tomorrow, or contact support.'
  461. );
  462. });
  463. it('throws error if user session has expired', async function () {
  464. const postRelocation = MockApiClient.addMockResponse({
  465. url: `/relocations/`,
  466. method: 'POST',
  467. statusCode: 401,
  468. });
  469. await waitForRenderSuccess('get-started');
  470. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  471. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  472. await userEvent.click(screen.getByRole('menuitemradio'));
  473. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  474. await waitForRenderSuccess('upload-backup');
  475. await userEvent.upload(
  476. screen.getByLabelText('file-upload'),
  477. new File(['hello'], 'hello.tar', {type: 'file'})
  478. );
  479. await userEvent.click(await screen.findByText('Start Relocation'));
  480. await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
  481. expect(addErrorMessage).toHaveBeenCalledWith('Your session has expired.');
  482. });
  483. it('throws error for 500 error', async function () {
  484. const postRelocation = MockApiClient.addMockResponse({
  485. url: `/relocations/`,
  486. method: 'POST',
  487. statusCode: 500,
  488. });
  489. await waitForRenderSuccess('get-started');
  490. await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
  491. await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth!.name);
  492. await userEvent.click(screen.getByRole('menuitemradio'));
  493. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  494. await waitForRenderSuccess('upload-backup');
  495. await userEvent.upload(
  496. screen.getByLabelText('file-upload'),
  497. new File(['hello'], 'hello.tar', {type: 'file'})
  498. );
  499. await userEvent.click(await screen.findByText('Start Relocation'));
  500. await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
  501. expect(addErrorMessage).toHaveBeenCalledWith(
  502. 'An error has occurred while trying to start relocation job. Please contact support for further assistance.'
  503. );
  504. });
  505. it('redirects to `get-started` page if expected local storage data is missing', async function () {
  506. sessionStorage.setItem('relocationOnboarding', JSON.stringify({}));
  507. await waitForRenderSuccess('upload-backup');
  508. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  509. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  510. expect(router.push).toHaveBeenCalledWith('/relocation/get-started/');
  511. });
  512. });
  513. describe('In Progress', function () {
  514. it('renders', async function () {
  515. MockApiClient.clearMockResponses();
  516. fetchExistingRelocations = MockApiClient.addMockResponse({
  517. url: '/relocations/',
  518. body: [
  519. {
  520. uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
  521. status: 'IN_PROGRESS',
  522. },
  523. ],
  524. });
  525. fetchPublicKeys = MockApiClient.addMockResponse({
  526. url: '/publickeys/relocations/',
  527. body: {
  528. public_key: fakePublicKey,
  529. },
  530. });
  531. await waitForRenderSuccess('in-progress');
  532. expect(
  533. await screen.findByText('Your relocation is under way!')
  534. ).toBeInTheDocument();
  535. });
  536. it('redirects to `get-started` page if there is no existing relocation', async function () {
  537. await waitForRenderSuccess('in-progress');
  538. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  539. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  540. expect(router.push).toHaveBeenCalledWith('/relocation/get-started/');
  541. });
  542. it('redirects to `get-started` page if there is no active relocation', async function () {
  543. MockApiClient.clearMockResponses();
  544. fetchExistingRelocations = MockApiClient.addMockResponse({
  545. url: '/relocations/',
  546. body: [
  547. {
  548. uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
  549. status: 'SUCCESS',
  550. },
  551. ],
  552. });
  553. fetchPublicKeys = MockApiClient.addMockResponse({
  554. url: '/publickeys/relocations/',
  555. body: {
  556. public_key: fakePublicKey,
  557. },
  558. });
  559. await waitForRenderSuccess('in-progress');
  560. await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
  561. await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
  562. expect(router.push).toHaveBeenCalledWith('/relocation/get-started/');
  563. });
  564. });
  565. });