123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- import {browserHistory} from 'react-router';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {
- fireEvent,
- render,
- screen,
- userEvent,
- waitFor,
- } from 'sentry-test/reactTestingLibrary';
- import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
- import ConfigStore from 'sentry/stores/configStore';
- import Relocation from 'sentry/views/relocation/relocation';
- jest.mock('sentry/actionCreators/indicator');
- const fakeOrgSlug = 'test-org';
- const fakePromoCode = 'free-hugs';
- const fakePublicKey = `FAKE-PK-ANY`;
- type FakeRegion = {
- name: string;
- publicKey: string;
- url: string;
- };
- const fakeRegions: {[key: string]: FakeRegion} = {
- Earth: {
- name: 'earth',
- url: 'https://earth.example.com',
- publicKey: 'FAKE-PK-EARTH',
- },
- Moon: {
- name: 'moon',
- url: 'https://moon.example.com',
- publicKey: 'FAKE-PK-MOON',
- },
- };
- describe('Relocation', function () {
- let fetchExistingRelocations: jest.Mock;
- let fetchPublicKeys: jest.Mock;
- beforeEach(function () {
- MockApiClient.clearMockResponses();
- MockApiClient.asyncDelay = undefined;
- sessionStorage.clear();
- ConfigStore.set('regions', [
- {name: fakeRegions.Earth.name, url: fakeRegions.Earth.url},
- {name: fakeRegions.Moon.name, url: fakeRegions.Moon.url},
- ]);
- ConfigStore.set('relocationConfig', {
- selectableRegions: [fakeRegions.Earth.name, fakeRegions.Moon.name],
- });
- // For tests that don't care about the difference between our "earth" and "moon" regions, we can
- // re-use the same mock responses, with the same generic public key for both.
- fetchExistingRelocations = MockApiClient.addMockResponse({
- url: '/relocations/',
- body: [],
- });
- fetchPublicKeys = MockApiClient.addMockResponse({
- url: '/publickeys/relocations/',
- body: {
- public_key: fakePublicKey,
- },
- });
- // The tests fail because we have a "component update was not wrapped in act" error. It should
- // be safe to ignore this error, but we should remove the mock once we move to react testing
- // library.
- //
- // eslint-disable-next-line no-console
- jest.spyOn(console, 'error').mockImplementation(jest.fn());
- });
- afterEach(function () {
- MockApiClient.clearMockResponses();
- MockApiClient.asyncDelay = undefined;
- sessionStorage.clear();
- });
- function renderPage(step) {
- const routeParams = {
- step,
- };
- const {routerProps, routerContext, organization} = initializeOrg({
- router: {
- params: routeParams,
- },
- });
- return render(<Relocation {...routerProps} />, {
- context: routerContext,
- organization,
- });
- }
- async function waitForRenderSuccess(step) {
- renderPage(step);
- await waitFor(() => expect(screen.getByTestId(step)).toBeInTheDocument());
- }
- async function waitForRenderError(step) {
- renderPage(step);
- await waitFor(() => expect(screen.getByTestId('loading-error')).toBeInTheDocument());
- }
- describe('Get Started', function () {
- it('renders', async function () {
- await waitForRenderSuccess('get-started');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(
- await screen.findByText('Basic information needed to get started')
- ).toBeInTheDocument();
- expect(
- await screen.findByText('Organization slugs being relocated')
- ).toBeInTheDocument();
- expect(await screen.findByText('Choose a datacenter location')).toBeInTheDocument();
- });
- it('redirects to `in-progress` page if user already has active relocation', async function () {
- MockApiClient.clearMockResponses();
- fetchExistingRelocations = MockApiClient.addMockResponse({
- url: '/relocations/',
- body: [
- {
- uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
- status: 'IN_PROGRESS',
- },
- ],
- });
- fetchPublicKeys = MockApiClient.addMockResponse({
- url: '/publickeys/relocations/',
- body: {
- public_key: fakePublicKey,
- },
- });
- await waitForRenderSuccess('get-started');
- await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(browserHistory.push).toHaveBeenCalledWith('/relocation/in-progress/');
- });
- it('should prevent user from going to the next step if no org slugs or region are entered', async function () {
- await waitForRenderSuccess('get-started');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(screen.getByRole('button', {name: 'Continue'})).toBeDisabled();
- });
- it('should be allowed to go to next step if org slug is entered, region is selected, and promo code is entered', async function () {
- await waitForRenderSuccess('get-started');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- const fetchPromoCode = MockApiClient.addMockResponse({
- url: `/promocodes-external/${fakePromoCode}`,
- method: 'GET',
- statusCode: 200,
- });
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
- await userEvent.click(screen.getByText('Got a promo code?', {exact: false}));
- await userEvent.type(screen.getByLabelText('promocode'), fakePromoCode);
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitFor(() => expect(fetchPromoCode).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).not.toHaveBeenCalled();
- });
- 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 () {
- await waitForRenderSuccess('get-started');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- const fetchPromoCode = MockApiClient.addMockResponse({
- url: `/promocodes-external/${fakePromoCode}`,
- method: 'GET',
- statusCode: 403,
- });
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
- await userEvent.click(screen.getByText('Got a promo code?', {exact: false}));
- await userEvent.type(screen.getByLabelText('promocode'), fakePromoCode);
- expect(screen.getByRole('button', {name: 'Continue'})).toBeEnabled();
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitFor(() => expect(fetchPromoCode).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).toHaveBeenCalledWith(
- 'That promotional code has already been claimed, does not have enough remaining uses, is no longer valid, or never existed.'
- );
- });
- it('should show loading indicator and error message if existing relocation retrieval failed', async function () {
- MockApiClient.clearMockResponses();
- // Note: only one fails, but that is enough.
- const failingFetchExistingEarthRelocation = MockApiClient.addMockResponse({
- host: fakeRegions.Earth.url,
- url: `/relocations/`,
- statusCode: 400,
- });
- const successfulFetchExistingMoonRelocation = MockApiClient.addMockResponse({
- host: fakeRegions.Moon.url,
- url: '/relocations/',
- body: [],
- });
- fetchPublicKeys = MockApiClient.addMockResponse({
- url: '/publickeys/relocations/',
- body: {
- public_key: fakePublicKey,
- },
- });
- await waitForRenderError('get-started');
- await waitFor(() =>
- expect(failingFetchExistingEarthRelocation).toHaveBeenCalledTimes(1)
- );
- await waitFor(() =>
- expect(successfulFetchExistingMoonRelocation).toHaveBeenCalledTimes(1)
- );
- expect(fetchPublicKeys).toHaveBeenCalledTimes(2);
- expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
- expect(screen.queryByLabelText('org-slugs')).not.toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
- const successfulFetchExistingEarthRelocation = MockApiClient.addMockResponse({
- host: fakeRegions.Earth.url,
- url: '/relocations/',
- body: [],
- });
- await userEvent.click(screen.getByRole('button', {name: 'Retry'}));
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- await waitFor(() => expect(screen.getByTestId('get-started')).toBeInTheDocument());
- await waitFor(() =>
- expect(successfulFetchExistingEarthRelocation).toHaveBeenCalledTimes(1)
- );
- await waitFor(() =>
- expect(successfulFetchExistingMoonRelocation).toHaveBeenCalledTimes(2)
- );
- expect(screen.queryByLabelText('org-slugs')).toBeInTheDocument();
- expect(screen.queryByRole('button', {name: 'Continue'})).toBeInTheDocument();
- });
- });
- describe('Public Key', function () {
- beforeEach(function () {
- sessionStorage.setItem(
- 'relocationOnboarding',
- JSON.stringify({
- orgSlugs: fakeOrgSlug,
- promoCode: fakePromoCode,
- regionUrl: fakeRegions.Earth.url,
- })
- );
- });
- it('should show instructions if key retrieval was successful', async function () {
- await waitForRenderSuccess('public-key');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(
- await screen.findByText("Save Sentry's public key to your machine")
- ).toBeInTheDocument();
- expect(screen.getByText('key.pub')).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Continue'})).toBeInTheDocument();
- });
- it('should show loading indicator if key retrieval still in progress', function () {
- MockApiClient.asyncDelay = 1;
- renderPage('public-key');
- expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
- expect(screen.queryByText('key.pub')).not.toBeInTheDocument();
- });
- it('should show loading indicator and error message if key retrieval failed', async function () {
- MockApiClient.clearMockResponses();
- fetchExistingRelocations = MockApiClient.addMockResponse({
- url: '/relocations/',
- body: [],
- });
- // Note: only one fails, but that is enough.
- const failingFetchEarthPublicKey = MockApiClient.addMockResponse({
- host: fakeRegions.Earth.url,
- url: `/publickeys/relocations/`,
- statusCode: 400,
- });
- const successfulFetchMoonPublicKey = MockApiClient.addMockResponse({
- host: fakeRegions.Moon.url,
- url: '/publickeys/relocations/',
- body: {
- public_key: fakeRegions.Moon.publicKey,
- },
- });
- await waitForRenderError('public-key');
- await waitFor(() => expect(failingFetchEarthPublicKey).toHaveBeenCalledTimes(1));
- await waitFor(() => expect(successfulFetchMoonPublicKey).toHaveBeenCalledTimes(1));
- expect(fetchExistingRelocations).toHaveBeenCalledTimes(2);
- expect(screen.queryByRole('button', {name: 'Continue'})).not.toBeInTheDocument();
- expect(screen.queryByText('key.pub')).not.toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
- const successfulFetchEarthPublicKey = MockApiClient.addMockResponse({
- host: fakeRegions.Earth.url,
- url: '/publickeys/relocations/',
- body: {
- public_key: fakeRegions.Earth.publicKey,
- },
- });
- await userEvent.click(screen.getByRole('button', {name: 'Retry'}));
- await waitFor(() => expect(successfulFetchEarthPublicKey).toHaveBeenCalledTimes(1));
- await waitFor(() => expect(successfulFetchMoonPublicKey).toHaveBeenCalledTimes(2));
- await waitFor(() => expect(screen.getByTestId('public-key')).toBeInTheDocument());
- expect(fetchExistingRelocations).toHaveBeenCalledTimes(2);
- expect(screen.queryByText('key.pub')).toBeInTheDocument();
- expect(screen.queryByRole('button', {name: 'Continue'})).toBeInTheDocument();
- });
- });
- describe('Encrypt Backup', function () {
- it('renders', async function () {
- await waitForRenderSuccess('encrypt-backup');
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(
- await screen.findByText(
- 'Create an encrypted backup of your current self-hosted instance'
- )
- ).toBeInTheDocument();
- });
- });
- describe('Upload Backup', function () {
- beforeEach(function () {
- sessionStorage.setItem(
- 'relocationOnboarding',
- JSON.stringify({
- orgSlugs: fakeOrgSlug,
- promoCode: fakePromoCode,
- regionUrl: fakeRegions.Earth.url,
- })
- );
- });
- it('renders', async function () {
- await waitForRenderSuccess('upload-backup');
- expect(
- await screen.findByText('Upload Tarball to begin the relocation process')
- ).toBeInTheDocument();
- });
- it('accepts a file upload', async function () {
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- expect(await screen.findByText('hello.tar')).toBeInTheDocument();
- expect(await screen.findByText('Start Relocation')).toBeInTheDocument();
- });
- it('accepts a file upload through drag and drop', async function () {
- await waitForRenderSuccess('upload-backup');
- fireEvent.drop(screen.getByLabelText('dropzone'), {
- dataTransfer: {files: [new File(['hello'], 'hello.tar', {type: 'file'})]},
- });
- expect(await screen.findByText('hello.tar')).toBeInTheDocument();
- expect(await screen.findByText('Start Relocation')).toBeInTheDocument();
- });
- it('correctly removes file and prompts for file upload', async function () {
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(screen.getByText('Remove file'));
- expect(screen.queryByText('hello.tar')).not.toBeInTheDocument();
- expect(
- await screen.findByText('Upload Tarball to begin the relocation process')
- ).toBeInTheDocument();
- });
- it('fails to starts relocation job if some form data is missing', async function () {
- // Remove `orgSlugs` from session storage; this will act as the "missing form data".
- sessionStorage.setItem(
- 'relocationOnboarding',
- JSON.stringify({
- promoCode: fakePromoCode,
- regionUrl: fakeRegions.Earth.url,
- })
- );
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- });
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() => expect(postRelocation).not.toHaveBeenCalled());
- expect(addErrorMessage).toHaveBeenCalledWith(
- 'An error has occurred while trying to start relocation job. Please contact support for further assistance.'
- );
- });
- it('starts relocation job if form data is available from previous steps', async function () {
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- responseJSON: [
- {
- uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
- status: 'IN_PROGRESS',
- },
- ],
- });
- await waitForRenderSuccess('get-started');
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() =>
- expect(postRelocation).toHaveBeenCalledWith(
- '/relocations/',
- expect.objectContaining({host: fakeRegions.Earth.url, method: 'POST'})
- )
- );
- expect(addSuccessMessage).toHaveBeenCalledWith(
- "Your relocation has started - we'll email you with updates as soon as we have 'em!"
- );
- await waitForRenderSuccess('in-progress');
- });
- it('throws error if user already has an in-progress relocation job', async function () {
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- statusCode: 409,
- });
- await waitForRenderSuccess('get-started');
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).toHaveBeenCalledWith(
- 'You already have an in-progress relocation job.'
- );
- });
- it('throws error if daily limit of relocations has been reached', async function () {
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- statusCode: 429,
- });
- await waitForRenderSuccess('get-started');
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).toHaveBeenCalledWith(
- 'We have reached the daily limit of relocations - please try again tomorrow, or contact support.'
- );
- });
- it('throws error if user session has expired', async function () {
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- statusCode: 401,
- });
- await waitForRenderSuccess('get-started');
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).toHaveBeenCalledWith('Your session has expired.');
- });
- it('throws error for 500 error', async function () {
- const postRelocation = MockApiClient.addMockResponse({
- url: `/relocations/`,
- method: 'POST',
- statusCode: 500,
- });
- await waitForRenderSuccess('get-started');
- await userEvent.type(screen.getByLabelText('org-slugs'), fakeOrgSlug);
- await userEvent.type(screen.getByLabelText('region'), fakeRegions.Earth.name);
- await userEvent.click(screen.getByRole('menuitemradio'));
- await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
- await waitForRenderSuccess('upload-backup');
- await userEvent.upload(
- screen.getByLabelText('file-upload'),
- new File(['hello'], 'hello.tar', {type: 'file'})
- );
- await userEvent.click(await screen.findByText('Start Relocation'));
- await waitFor(() => expect(postRelocation).toHaveBeenCalledTimes(1));
- expect(addErrorMessage).toHaveBeenCalledWith(
- 'An error has occurred while trying to start relocation job. Please contact support for further assistance.'
- );
- });
- });
- describe('In Progress', function () {
- it('renders', async function () {
- MockApiClient.clearMockResponses();
- fetchExistingRelocations = MockApiClient.addMockResponse({
- url: '/relocations/',
- body: [
- {
- uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
- status: 'IN_PROGRESS',
- },
- ],
- });
- fetchPublicKeys = MockApiClient.addMockResponse({
- url: '/publickeys/relocations/',
- body: {
- public_key: fakePublicKey,
- },
- });
- await waitForRenderSuccess('in-progress');
- expect(
- await screen.findByText('Your relocation is under way!')
- ).toBeInTheDocument();
- });
- it('redirects to `get-started` page if there is no existing relocation', async function () {
- await waitForRenderSuccess('in-progress');
- await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(browserHistory.push).toHaveBeenCalledWith('/relocation/get-started/');
- });
- it('redirects to `get-started` page if there is no active relocation', async function () {
- MockApiClient.clearMockResponses();
- fetchExistingRelocations = MockApiClient.addMockResponse({
- url: '/relocations/',
- body: [
- {
- uuid: 'ccef828a-03d8-4dd0-918a-487ffecf8717',
- status: 'SUCCESS',
- },
- ],
- });
- fetchPublicKeys = MockApiClient.addMockResponse({
- url: '/publickeys/relocations/',
- body: {
- public_key: fakePublicKey,
- },
- });
- await waitForRenderSuccess('in-progress');
- await waitFor(() => expect(fetchExistingRelocations).toHaveBeenCalledTimes(2));
- await waitFor(() => expect(fetchPublicKeys).toHaveBeenCalledTimes(2));
- expect(browserHistory.push).toHaveBeenCalledWith('/relocation/get-started/');
- });
- });
- });
|