import pick from 'lodash/pick'; import {ConfigFixture} from 'sentry-fixture/config'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; import {SentryAppFixture} from 'sentry-fixture/sentryApp'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; import {textWithMarkupMatcher} from 'sentry-test/utils'; import ConfigStore from 'sentry/stores/configStore'; import type {Organization as TOrganization} from 'sentry/types/organization'; import {generateOrgSlugUrl} from 'sentry/utils'; import SentryAppExternalInstallation from 'sentry/views/sentryAppExternalInstallation'; describe('SentryAppExternalInstallation', () => { let sentryApp: ReturnType, getOrgsMock: ReturnType, getOrgMock: ReturnType, getAppMock: ReturnType, getInstallationsMock: ReturnType, getFeaturesMock: ReturnType, org1: TOrganization, org1Lite: Pick, org2: TOrganization, org2Lite: Pick; beforeEach(() => { MockApiClient.clearMockResponses(); org1 = OrganizationFixture({ slug: 'org1', name: 'Organization 1', }); org2 = OrganizationFixture({ slug: 'org2', name: 'Organization 2', }); org1Lite = pick(org1, ['slug', 'name', 'id']); org2Lite = pick(org2, ['slug', 'name', 'id']); sentryApp = SentryAppFixture({ status: 'published', redirectUrl: 'https://google.com', }); getAppMock = MockApiClient.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); getFeaturesMock = MockApiClient.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/features/`, body: [], }); MockApiClient.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/interaction/`, method: 'POST', statusCode: 200, body: {}, }); }); describe('single organization', () => { beforeEach(() => { getOrgsMock = MockApiClient.addMockResponse({ url: '/organizations/', body: [org1Lite], }); getOrgMock = MockApiClient.addMockResponse({ url: `/organizations/${org1.slug}/`, body: org1, }); getInstallationsMock = MockApiClient.addMockResponse({ url: `/organizations/${org1.slug}/sentry-app-installations/`, body: [], }); window.__initialData = ConfigFixture({ customerDomain: { subdomain: 'org1', organizationUrl: 'https://org1.sentry.io', sentryUrl: 'https://sentry.io', }, links: { ...(window.__initialData?.links ?? {}), sentryUrl: 'https://sentry.io', }, }); ConfigStore.loadInitialData(window.__initialData); }); it('sets the org automatically', async () => { render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); expect( await screen.findByText( textWithMarkupMatcher( 'You are installing Sample App for organization Organization 1' ) ) ).toBeInTheDocument(); expect(getAppMock).toHaveBeenCalled(); expect(getOrgsMock).toHaveBeenCalled(); expect(getOrgMock).toHaveBeenCalled(); expect(getInstallationsMock).toHaveBeenCalled(); expect(screen.queryByText('Select an organization')).not.toBeInTheDocument(); }); it('installs and redirects', async () => { const installUrl = `/organizations/${org1.slug}/sentry-app-installations/`; const install = { uuid: 'fake-id', code: 'some-code', }; const installMock = MockApiClient.addMockResponse({ url: installUrl, method: 'POST', body: install, }); render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); await userEvent.click(await screen.findByTestId('install')); // failing currently expect(installMock).toHaveBeenCalledWith( installUrl, expect.objectContaining({ data: {slug: sentryApp.slug}, }) ); await waitFor(() => { expect(window.location.assign).toHaveBeenCalledWith( `https://google.com/?code=${install.code}&installationId=${install.uuid}&orgSlug=${org1.slug}` ); }); jest.mocked(window.location.assign).mockClear(); }); it('installs and redirects with state', async () => { const installUrl = `/organizations/${org1.slug}/sentry-app-installations/`; const install = { uuid: 'fake-id', code: 'some-code', }; const installMock = MockApiClient.addMockResponse({ url: installUrl, method: 'POST', body: install, }); const state = 'some-state'; const location = { ...RouteComponentPropsFixture(), location: { ...RouteComponentPropsFixture().location, query: {state}, }, }; render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); await userEvent.click(await screen.findByTestId('install')); // failing currently expect(installMock).toHaveBeenCalledWith( installUrl, expect.objectContaining({ data: {slug: sentryApp.slug}, }) ); await waitFor(() => { expect(window.location.assign).toHaveBeenCalledWith( `https://google.com/?code=${install.code}&installationId=${install.uuid}&orgSlug=${org1.slug}&state=${state}` ); }); jest.mocked(window.location.assign).mockClear(); }); }); describe('multiple organizations', () => { beforeEach(() => { getOrgsMock = MockApiClient.addMockResponse({ url: '/organizations/', body: [org1Lite, org2Lite], }); getOrgMock = MockApiClient.addMockResponse({ url: `/organizations/${org1.slug}/`, body: org1, }); getInstallationsMock = MockApiClient.addMockResponse({ url: `/organizations/${org1.slug}/sentry-app-installations/`, body: [], }); window.__initialData = ConfigFixture({ customerDomain: { subdomain: 'org1', organizationUrl: 'https://org1.sentry.io', sentryUrl: 'https://sentry.io', }, links: { ...(window.__initialData?.links ?? {}), sentryUrl: 'https://sentry.io', }, }); ConfigStore.loadInitialData(window.__initialData); }); it('sets the org automatically', async () => { render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); expect(getAppMock).toHaveBeenCalled(); expect(getOrgsMock).toHaveBeenCalled(); expect(getOrgMock).toHaveBeenCalled(); expect(getInstallationsMock).toHaveBeenCalled(); expect(screen.queryByText('Select an organization')).not.toBeInTheDocument(); await waitFor(() => expect(screen.getByTestId('install')).toBeEnabled()); }); it('loads orgs from multiple regions', async () => { window.__initialData = { ...window.__initialData, memberRegions: [ {name: 'us', url: 'https://us.example.org'}, {name: 'de', url: 'https://de.example.org'}, ], }; ConfigStore.loadInitialData(window.__initialData); const deorg = OrganizationFixture({slug: 'de-org'}); const getDeOrgs = MockApiClient.addMockResponse({ url: '/organizations/', body: [deorg], match: [ function (_url: string, options: Record) { return options.host === 'https://de.example.org'; }, ], }); render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); expect(getDeOrgs).toHaveBeenCalled(); }); it('selecting org changes the url', async () => { const preselectedOrg = OrganizationFixture(); const {routerProps} = initializeOrg({organization: preselectedOrg}); window.__initialData = ConfigFixture({ customerDomain: { subdomain: 'org1', organizationUrl: 'https://org1.sentry.io', sentryUrl: 'https://sentry.io', }, links: { ...(window.__initialData?.links ?? {}), sentryUrl: 'https://sentry.io', }, }); ConfigStore.loadInitialData(window.__initialData); getOrgMock = MockApiClient.addMockResponse({ url: `/organizations/org1/`, body: preselectedOrg, }); getInstallationsMock = MockApiClient.addMockResponse({ url: `/organizations/${org1.slug}/sentry-app-installations/`, body: [], }); render( ); await waitFor(() => expect(getInstallationsMock).toHaveBeenCalled()); await selectEvent.select(screen.getByRole('textbox'), 'org2'); expect(window.location.assign).toHaveBeenCalledWith(generateOrgSlugUrl('org2')); expect(getFeaturesMock).toHaveBeenCalled(); }); }); });