index.spec.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import {browserHistory} from 'react-router';
  2. import {ConfigFixture} from 'sentry-fixture/config';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
  5. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  6. import {logout} from 'sentry/actionCreators/account';
  7. import AcceptOrganizationInvite from 'sentry/views/acceptOrganizationInvite';
  8. jest.mock('sentry/actionCreators/account');
  9. const addMock = body =>
  10. MockApiClient.addMockResponse({
  11. url: '/accept-invite/org-slug/1/abc/',
  12. method: 'GET',
  13. body,
  14. });
  15. const getJoinButton = () => {
  16. const maybeButton = screen.queryByRole('button', {
  17. name: 'Join the org-slug organization',
  18. });
  19. return maybeButton;
  20. };
  21. describe('AcceptOrganizationInvite', function () {
  22. const organization = OrganizationFixture({slug: 'org-slug'});
  23. const initialData = window.__initialData;
  24. afterEach(() => {
  25. window.__initialData = initialData;
  26. });
  27. it('can accept invitation', async function () {
  28. addMock({
  29. orgSlug: organization.slug,
  30. needsAuthentication: false,
  31. needs2fa: false,
  32. hasAuthProvider: false,
  33. requireSso: false,
  34. existingMember: false,
  35. });
  36. render(
  37. <AcceptOrganizationInvite
  38. {...RouteComponentPropsFixture()}
  39. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  40. />
  41. );
  42. const acceptMock = MockApiClient.addMockResponse({
  43. url: '/accept-invite/org-slug/1/abc/',
  44. method: 'POST',
  45. });
  46. const joinButton = getJoinButton();
  47. await userEvent.click(joinButton!);
  48. expect(acceptMock).toHaveBeenCalled();
  49. expect(browserHistory.replace).toHaveBeenCalledWith('/org-slug/');
  50. });
  51. it('can accept invitation on customer-domains', async function () {
  52. window.__initialData = ConfigFixture({
  53. customerDomain: {
  54. subdomain: 'org-slug',
  55. organizationUrl: 'https://org-slug.sentry.io',
  56. sentryUrl: 'https://sentry.io',
  57. },
  58. links: {
  59. ...(window.__initialData?.links ?? {}),
  60. sentryUrl: 'https://sentry.io',
  61. },
  62. });
  63. addMock({
  64. orgSlug: organization.slug,
  65. needsAuthentication: false,
  66. needs2fa: false,
  67. hasAuthProvider: false,
  68. requireSso: false,
  69. existingMember: false,
  70. });
  71. render(
  72. <AcceptOrganizationInvite
  73. {...RouteComponentPropsFixture()}
  74. params={{memberId: '1', token: 'abc'}}
  75. />
  76. );
  77. const acceptMock = MockApiClient.addMockResponse({
  78. url: '/accept-invite/org-slug/1/abc/',
  79. method: 'POST',
  80. });
  81. const joinButton = getJoinButton();
  82. await userEvent.click(joinButton!);
  83. expect(acceptMock).toHaveBeenCalled();
  84. expect(browserHistory.replace).toHaveBeenCalledWith('/org-slug/');
  85. });
  86. it('requires authentication to join', function () {
  87. addMock({
  88. orgSlug: organization.slug,
  89. needsAuthentication: true,
  90. needs2fa: false,
  91. hasAuthProvider: false,
  92. requireSso: false,
  93. existingMember: false,
  94. });
  95. render(
  96. <AcceptOrganizationInvite
  97. {...RouteComponentPropsFixture()}
  98. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  99. />
  100. );
  101. expect(getJoinButton()).not.toBeInTheDocument();
  102. expect(screen.getByTestId('action-info-general')).toBeInTheDocument();
  103. expect(screen.queryByTestId('action-info-sso')).not.toBeInTheDocument();
  104. expect(
  105. screen.getByRole('button', {name: 'Create a new account'})
  106. ).toBeInTheDocument();
  107. expect(
  108. screen.getByRole('link', {name: 'Login using an existing account'})
  109. ).toBeInTheDocument();
  110. });
  111. it('suggests sso authentication to login', function () {
  112. addMock({
  113. orgSlug: organization.slug,
  114. needsAuthentication: true,
  115. needs2fa: false,
  116. hasAuthProvider: true,
  117. requireSso: false,
  118. existingMember: false,
  119. ssoProvider: 'SSO',
  120. });
  121. render(
  122. <AcceptOrganizationInvite
  123. {...RouteComponentPropsFixture()}
  124. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  125. />
  126. );
  127. expect(getJoinButton()).not.toBeInTheDocument();
  128. expect(screen.getByTestId('action-info-general')).toBeInTheDocument();
  129. expect(screen.getByTestId('action-info-sso')).toBeInTheDocument();
  130. expect(screen.getByRole('button', {name: 'Join with SSO'})).toBeInTheDocument();
  131. expect(
  132. screen.getByRole('button', {name: 'Create a new account'})
  133. ).toBeInTheDocument();
  134. expect(
  135. screen.getByRole('link', {name: 'Login using an existing account'})
  136. ).toBeInTheDocument();
  137. });
  138. it('enforce required sso authentication', function () {
  139. addMock({
  140. orgSlug: organization.slug,
  141. needsAuthentication: true,
  142. needs2fa: false,
  143. hasAuthProvider: true,
  144. requireSso: true,
  145. existingMember: false,
  146. ssoProvider: 'SSO',
  147. });
  148. render(
  149. <AcceptOrganizationInvite
  150. {...RouteComponentPropsFixture()}
  151. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  152. />
  153. );
  154. expect(getJoinButton()).not.toBeInTheDocument();
  155. expect(screen.queryByTestId('action-info-general')).not.toBeInTheDocument();
  156. expect(screen.getByTestId('action-info-sso')).toBeInTheDocument();
  157. expect(screen.getByRole('button', {name: 'Join with SSO'})).toBeInTheDocument();
  158. expect(
  159. screen.queryByRole('button', {name: 'Create a new account'})
  160. ).not.toBeInTheDocument();
  161. expect(
  162. screen.queryByRole('link', {name: 'Login using an existing account'})
  163. ).not.toBeInTheDocument();
  164. });
  165. it('enforce required sso authentication for logged in users', function () {
  166. addMock({
  167. orgSlug: organization.slug,
  168. needsAuthentication: false,
  169. needs2fa: false,
  170. hasAuthProvider: true,
  171. requireSso: true,
  172. existingMember: false,
  173. ssoProvider: 'SSO',
  174. });
  175. render(
  176. <AcceptOrganizationInvite
  177. {...RouteComponentPropsFixture()}
  178. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  179. />
  180. );
  181. expect(getJoinButton()).not.toBeInTheDocument();
  182. expect(screen.queryByTestId('action-info-general')).not.toBeInTheDocument();
  183. expect(screen.getByTestId('action-info-sso')).toBeInTheDocument();
  184. expect(screen.getByRole('button', {name: 'Join with SSO'})).toBeInTheDocument();
  185. expect(
  186. screen.queryByRole('button', {name: 'Create a new account'})
  187. ).not.toBeInTheDocument();
  188. expect(
  189. screen.queryByRole('link', {name: 'Login using an existing account'})
  190. ).not.toBeInTheDocument();
  191. });
  192. it('show logout button for logged in users w/ sso and membership', async function () {
  193. addMock({
  194. orgSlug: organization.slug,
  195. needsAuthentication: false,
  196. needs2fa: false,
  197. hasAuthProvider: true,
  198. requireSso: true,
  199. existingMember: true,
  200. ssoProvider: 'SSO',
  201. });
  202. render(
  203. <AcceptOrganizationInvite
  204. {...RouteComponentPropsFixture()}
  205. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  206. />
  207. );
  208. expect(screen.getByTestId('existing-member')).toBeInTheDocument();
  209. await userEvent.click(screen.getByTestId('existing-member-link'));
  210. expect(logout).toHaveBeenCalled();
  211. await waitFor(() => expect(window.location.replace).toHaveBeenCalled());
  212. });
  213. it('shows right options for logged in user and optional SSO', function () {
  214. addMock({
  215. orgSlug: organization.slug,
  216. needsAuthentication: false,
  217. needs2fa: false,
  218. hasAuthProvider: true,
  219. requireSso: false,
  220. existingMember: false,
  221. ssoProvider: 'SSO',
  222. });
  223. render(
  224. <AcceptOrganizationInvite
  225. {...RouteComponentPropsFixture()}
  226. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  227. />
  228. );
  229. expect(screen.getByTestId('action-info-sso')).toBeInTheDocument();
  230. expect(getJoinButton()).toBeInTheDocument();
  231. });
  232. it('shows a logout button for existing members', async function () {
  233. addMock({
  234. orgSlug: organization.slug,
  235. needsAuthentication: false,
  236. needs2fa: false,
  237. hasAuthProvider: false,
  238. requireSso: false,
  239. existingMember: true,
  240. });
  241. render(
  242. <AcceptOrganizationInvite
  243. {...RouteComponentPropsFixture()}
  244. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  245. />
  246. );
  247. expect(screen.getByTestId('existing-member')).toBeInTheDocument();
  248. await userEvent.click(screen.getByTestId('existing-member-link'));
  249. expect(logout).toHaveBeenCalled();
  250. await waitFor(() => expect(window.location.replace).toHaveBeenCalled());
  251. });
  252. it('shows 2fa warning', function () {
  253. addMock({
  254. orgSlug: organization.slug,
  255. needsAuthentication: false,
  256. needs2fa: true,
  257. hasAuthProvider: false,
  258. requireSso: false,
  259. existingMember: false,
  260. });
  261. render(
  262. <AcceptOrganizationInvite
  263. {...RouteComponentPropsFixture()}
  264. params={{orgId: 'org-slug', memberId: '1', token: 'abc'}}
  265. />
  266. );
  267. expect(
  268. screen.getByRole('button', {name: 'Configure Two-Factor Auth'})
  269. ).toBeInTheDocument();
  270. });
  271. });