import {Component} from 'react'; import type {RouteContextInterface} from 'react-router'; import {LocationFixture} from 'sentry-fixture/locationFixture'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; import {RouterFixture} from 'sentry-fixture/routerFixture'; import {TeamFixture} from 'sentry-fixture/team'; import {UserFixture} from 'sentry-fixture/user'; import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import * as orgsActionCreators from 'sentry/actionCreators/organizations'; import {openSudo} from 'sentry/actionCreators/sudoModal'; import {SentryPropTypeValidators} from 'sentry/sentryPropTypeValidators'; import ConfigStore from 'sentry/stores/configStore'; import OrganizationStore from 'sentry/stores/organizationStore'; import ProjectsStore from 'sentry/stores/projectsStore'; import TeamStore from 'sentry/stores/teamStore'; import type {Organization} from 'sentry/types'; import useOrganization from 'sentry/utils/useOrganization'; import {OrganizationContextProvider, useEnsureOrganization} from './organizationContext'; import {RouteContext} from './routeContext'; jest.mock('sentry/actionCreators/sudoModal'); describe('OrganizationContext', function () { let getOrgMock: jest.Mock; let getProjectsMock: jest.Mock; let getTeamsMock: jest.Mock; const organization = OrganizationFixture(); const project = ProjectFixture(); const team = TeamFixture(); const router: RouteContextInterface = { router: RouterFixture(), location: LocationFixture(), routes: [], params: {orgId: organization.slug}, }; function setupOrgMocks(org: Organization) { const orgMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/`, body: org, }); const projectMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/projects/`, body: [project], }); const teamMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/teams/`, body: [team], }); return {orgMock, projectMock, teamMock}; } beforeEach(function () { MockApiClient.clearMockResponses(); const {orgMock, projectMock, teamMock} = setupOrgMocks(organization); getOrgMock = orgMock; getProjectsMock = projectMock; getTeamsMock = teamMock; jest.spyOn(TeamStore, 'loadInitialData'); jest.spyOn(ProjectsStore, 'loadInitialData'); ConfigStore.init(); OrganizationStore.reset(); jest.spyOn(console, 'error').mockImplementation(jest.fn()); }); afterEach(function () { // eslint-disable-next-line no-console jest.mocked(console.error).mockRestore(); }); function OrganizationLoaderStub() { useEnsureOrganization(); return null; } /** * Used to test that the organization context is propegated */ function OrganizationName() { const org = useOrganization({allowNull: true}); return
{org?.slug ?? 'no-org'}
; } /** * Used to test the legacy organization context behavior */ class OrganizationNameLegacyConsumer extends Component { static contextTypes = { organization: SentryPropTypeValidators.isOrganization, }; declare context: {organization: Organization | undefined}; render() { return
{this.context.organization?.slug ?? 'no-org'}
; } } it('fetches org, projects, teams, and provides organization context', async function () { render( ); expect(await screen.findByText(organization.slug)).toBeInTheDocument(); expect(getOrgMock).toHaveBeenCalled(); expect(getProjectsMock).toHaveBeenCalled(); expect(getTeamsMock).toHaveBeenCalled(); }); it('provides legacy organization context', async function () { render( ); expect(await screen.findByText(organization.slug)).toBeInTheDocument(); }); it('does not fetch if organization is already set', async function () { OrganizationStore.onUpdate(organization); render( ); expect(await screen.findByText(organization.slug)).toBeInTheDocument(); expect(getOrgMock).not.toHaveBeenCalled(); }); it('fetches new org when router params change', async function () { // First render with org-slug const {rerender} = render( ); expect(await screen.findByText(organization.slug)).toBeInTheDocument(); const anotherOrg = OrganizationFixture({slug: 'another-org'}); const {orgMock, projectMock, teamMock} = setupOrgMocks(anotherOrg); const switchOrganization = jest.spyOn(orgsActionCreators, 'switchOrganization'); // re-render with another-org rerender( ); expect(await screen.findByText(anotherOrg.slug)).toBeInTheDocument(); expect(orgMock).toHaveBeenCalled(); expect(projectMock).toHaveBeenCalled(); expect(teamMock).toHaveBeenCalled(); expect(switchOrganization).toHaveBeenCalled(); }); it('opens sudo modal for superusers on 403s', async function () { ConfigStore.set('user', UserFixture({isSuperuser: true})); getOrgMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/', statusCode: 403, }); render( ); await waitFor(() => !OrganizationStore.getState().loading); // eslint-disable-next-line no-console expect(console.error).toHaveBeenCalled(); expect(openSudo).toHaveBeenCalled(); }); /** * This test will rarely happen since most configurations are now using customer domains */ it('uses last organization slug from ConfigStore', async function () { const configStoreOrg = OrganizationFixture({slug: 'config-store-org'}); ConfigStore.set('lastOrganization', configStoreOrg.slug); const {orgMock, projectMock, teamMock} = setupOrgMocks(configStoreOrg); // orgId is not present in the router. render( ); expect(await screen.findByText(configStoreOrg.slug)).toBeInTheDocument(); expect(orgMock).toHaveBeenCalled(); expect(projectMock).toHaveBeenCalled(); expect(teamMock).toHaveBeenCalled(); }); });