withDomainRequired.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. import type {RouteComponentProps} from 'react-router';
  2. import {LocationFixture} from 'sentry-fixture/locationFixture';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {render, screen} from 'sentry-test/reactTestingLibrary';
  6. import withDomainRequired, {normalizeUrl} from 'sentry/utils/withDomainRequired';
  7. describe('normalizeUrl', function () {
  8. let result;
  9. beforeEach(function () {
  10. window.__initialData = {
  11. customerDomain: {
  12. subdomain: 'albertos-apples',
  13. organizationUrl: 'https://albertos-apples.sentry.io',
  14. sentryUrl: 'https://sentry.io',
  15. },
  16. } as any;
  17. });
  18. it('replaces paths in strings', function () {
  19. const location = LocationFixture();
  20. const cases = [
  21. // input, expected
  22. ['/settings/', '/settings/'],
  23. ['/accept-terms/acme/', '/accept-terms/'],
  24. // Organization settings views.
  25. ['/settings/acme/', '/settings/organization/'],
  26. ['/settings/organization', '/settings/organization/'],
  27. ['/settings/sentry-organizations/members/', '/settings/members/'],
  28. ['/settings/sentry-organizations/members/3/', '/settings/members/3/'],
  29. ['/settings/sentry-organizations/teams/peeps/', '/settings/teams/peeps/'],
  30. ['/settings/sentry-organizations/teams/payments/', '/settings/teams/payments/'],
  31. ['/settings/sentry-organizations/billing/receipts/', '/settings/billing/receipts/'],
  32. [
  33. '/settings/sentry-organizations/teams/test-organizations/notifications/',
  34. '/settings/teams/test-organizations/notifications/',
  35. ],
  36. [
  37. '/settings/acme/developer-settings/release-bot/',
  38. '/settings/developer-settings/release-bot/',
  39. ],
  40. [
  41. '/settings/sentry-organizations/integrations/vercel/12345/?next=something',
  42. '/settings/integrations/vercel/12345/?next=something',
  43. ],
  44. // Settings views for orgs with acccount/billing in their slugs.
  45. ['/settings/account-on/', '/settings/organization/'],
  46. ['/settings/billing-co/', '/settings/organization/'],
  47. ['/settings/account-on/integrations/', '/settings/integrations/'],
  48. [
  49. '/settings/account-on/projects/billing-app/source-maps/',
  50. '/settings/projects/billing-app/source-maps/',
  51. ],
  52. ['/settings/billing-co/integrations/', '/settings/integrations/'],
  53. [
  54. '/settings/billing-co/projects/billing-app/source-maps/',
  55. '/settings/projects/billing-app/source-maps/',
  56. ],
  57. // Account settings should stay the same
  58. ['/settings/account/', '/settings/account/'],
  59. ['/settings/account/security/', '/settings/account/security/'],
  60. ['/settings/account/details/', '/settings/account/details/'],
  61. ['/join-request/acme', '/join-request/'],
  62. ['/join-request/acme/', '/join-request/'],
  63. ['/onboarding/acme/', '/onboarding/'],
  64. ['/onboarding/acme/project/', '/onboarding/project/'],
  65. ['/organizations/new/', '/organizations/new/'],
  66. ['/organizations/albertos-organizations/issues/', '/issues/'],
  67. [
  68. '/organizations/albertos-organizations/issues/?_q=all#hash',
  69. '/issues/?_q=all#hash',
  70. ],
  71. ['/acme/project-slug/getting-started/', '/getting-started/project-slug/'],
  72. [
  73. '/acme/project-slug/getting-started/python',
  74. '/getting-started/project-slug/python',
  75. ],
  76. ['/settings/projects/python/filters/', '/settings/projects/python/filters/'],
  77. ['/settings/projects/onboarding/abc123/', '/settings/projects/onboarding/abc123/'],
  78. [
  79. '/settings/projects/join-request/abc123/',
  80. '/settings/projects/join-request/abc123/',
  81. ],
  82. [
  83. '/settings/projects/python/filters/discarded/',
  84. '/settings/projects/python/filters/discarded/',
  85. ],
  86. [
  87. '/settings/projects/getting-started/abc123/',
  88. '/settings/projects/getting-started/abc123/',
  89. ],
  90. // Team settings links in breadcrumbs can be pre-normalized from breadcrumbs
  91. ['/settings/teams/peeps/', '/settings/teams/peeps/'],
  92. [
  93. '/settings/billing/checkout/?_q=all#hash',
  94. '/settings/billing/checkout/?_q=all#hash',
  95. ],
  96. [
  97. '/settings/billing/bundle-checkout/?_q=all#hash',
  98. '/settings/billing/bundle-checkout/?_q=all#hash',
  99. ],
  100. ];
  101. for (const [input, expected] of cases) {
  102. result = normalizeUrl(input);
  103. expect(result).toEqual(expected);
  104. result = normalizeUrl(input, location);
  105. expect(result).toEqual(expected);
  106. result = normalizeUrl(input, {forceCustomerDomain: false});
  107. expect(result).toEqual(expected);
  108. result = normalizeUrl(input, location, {forceCustomerDomain: false});
  109. expect(result).toEqual(expected);
  110. }
  111. // Normalizes urls if options.customerDomain is true and orgslug.sentry.io isn't being used
  112. window.__initialData.customerDomain = null;
  113. for (const [input, expected] of cases) {
  114. result = normalizeUrl(input, {forceCustomerDomain: true});
  115. expect(result).toEqual(expected);
  116. result = normalizeUrl(input, location, {forceCustomerDomain: true});
  117. expect(result).toEqual(expected);
  118. }
  119. // No effect if customerDomain isn't defined
  120. window.__initialData.customerDomain = null;
  121. for (const [input, _expected] of cases) {
  122. result = normalizeUrl(input);
  123. expect(result).toEqual(input);
  124. result = normalizeUrl(input, location);
  125. expect(result).toEqual(input);
  126. }
  127. });
  128. it('replaces pathname in objects', function () {
  129. const location = LocationFixture();
  130. result = normalizeUrl({pathname: '/settings/acme/'}, location);
  131. expect(result.pathname).toEqual('/settings/organization/');
  132. result = normalizeUrl({pathname: '/settings/acme/'}, location, {
  133. forceCustomerDomain: false,
  134. });
  135. expect(result.pathname).toEqual('/settings/organization/');
  136. result = normalizeUrl({pathname: '/settings/sentry/members'}, location);
  137. expect(result.pathname).toEqual('/settings/members');
  138. result = normalizeUrl({pathname: '/organizations/albertos-apples/issues'}, location);
  139. expect(result.pathname).toEqual('/issues');
  140. result = normalizeUrl(
  141. {
  142. pathname: '/organizations/sentry/profiling/profile/sentry/abc123/',
  143. query: {sorting: 'call order'},
  144. },
  145. location
  146. );
  147. expect(result.pathname).toEqual('/profiling/profile/sentry/abc123/');
  148. result = normalizeUrl(
  149. {
  150. pathname: '/organizations/albertos-apples/issues',
  151. query: {q: 'all'},
  152. },
  153. location
  154. );
  155. expect(result.pathname).toEqual('/issues');
  156. // Normalizes urls if options.customerDomain is true and orgslug.sentry.io isn't being used
  157. window.__initialData.customerDomain = null;
  158. result = normalizeUrl({pathname: '/settings/acme/'}, location, {
  159. forceCustomerDomain: true,
  160. });
  161. expect(result.pathname).toEqual('/settings/organization/');
  162. result = normalizeUrl(
  163. {
  164. pathname: '/organizations/albertos-apples/issues',
  165. query: {q: 'all'},
  166. },
  167. location,
  168. {
  169. forceCustomerDomain: true,
  170. }
  171. );
  172. expect(result.pathname).toEqual('/issues');
  173. });
  174. });
  175. const originalLocation = window.location;
  176. describe('withDomainRequired', function () {
  177. type Props = RouteComponentProps<{orgId: string}, {}>;
  178. function MyComponent(props: Props) {
  179. const {params} = props;
  180. return <div>Org slug: {params.orgId ?? 'no org slug'}</div>;
  181. }
  182. beforeEach(function () {
  183. Object.defineProperty(window, 'location', {
  184. writable: true,
  185. value: {
  186. replace: jest.fn(),
  187. pathname: '/organizations/albertos-apples/issues/',
  188. search: '?q=123',
  189. hash: '#hash',
  190. },
  191. });
  192. window.__initialData = {
  193. customerDomain: {
  194. subdomain: 'albertos-apples',
  195. organizationUrl: 'https://albertos-apples.sentry.io',
  196. sentryUrl: 'https://sentry.io',
  197. },
  198. links: {
  199. organizationUrl: null,
  200. regionUrl: null,
  201. sentryUrl: 'https://sentry.io',
  202. },
  203. } as any;
  204. });
  205. afterEach(function () {
  206. window.location = originalLocation;
  207. });
  208. it('redirects to sentryUrl in non-customer domain world', function () {
  209. window.__initialData = {
  210. customerDomain: null,
  211. features: ['organizations:customer-domains'],
  212. links: {
  213. organizationUrl: null,
  214. regionUrl: null,
  215. sentryUrl: 'https://sentry.io',
  216. },
  217. } as any;
  218. const organization = OrganizationFixture({
  219. slug: 'albertos-apples',
  220. features: [],
  221. });
  222. const params = {
  223. orgId: 'albertos-apples',
  224. };
  225. const {router} = initializeOrg({
  226. organization,
  227. router: {
  228. params,
  229. },
  230. });
  231. const WrappedComponent = withDomainRequired(MyComponent);
  232. const {container} = render(
  233. <WrappedComponent
  234. router={router}
  235. location={router.location}
  236. params={params}
  237. routes={router.routes}
  238. routeParams={router.params}
  239. route={{}}
  240. />,
  241. {router}
  242. );
  243. expect(container).toBeEmptyDOMElement();
  244. expect(window.location.replace).toHaveBeenCalledTimes(1);
  245. expect(window.location.replace).toHaveBeenCalledWith(
  246. 'https://sentry.io/organizations/albertos-apples/issues/?q=123#hash'
  247. );
  248. });
  249. it('redirects to sentryUrl if customer-domains is omitted', function () {
  250. window.__initialData = {
  251. customerDomain: {
  252. subdomain: 'albertos-apples',
  253. organizationUrl: 'https://albertos-apples.sentry.io',
  254. sentryUrl: 'https://sentry.io',
  255. },
  256. features: [],
  257. links: {
  258. organizationUrl: null,
  259. regionUrl: null,
  260. sentryUrl: 'https://sentry.io',
  261. },
  262. } as any;
  263. const organization = OrganizationFixture({
  264. slug: 'albertos-apples',
  265. features: [],
  266. });
  267. const params = {
  268. orgId: 'albertos-apples',
  269. };
  270. const {router} = initializeOrg({
  271. organization,
  272. router: {
  273. params,
  274. },
  275. });
  276. const WrappedComponent = withDomainRequired(MyComponent);
  277. const {container} = render(
  278. <WrappedComponent
  279. router={router}
  280. location={router.location}
  281. params={params}
  282. routes={router.routes}
  283. routeParams={router.params}
  284. route={{}}
  285. />,
  286. {router}
  287. );
  288. expect(container).toBeEmptyDOMElement();
  289. expect(window.location.replace).toHaveBeenCalledTimes(1);
  290. expect(window.location.replace).toHaveBeenCalledWith(
  291. 'https://sentry.io/organizations/albertos-apples/issues/?q=123#hash'
  292. );
  293. });
  294. it('renders when window.__initialData.customerDomain and customer-domains feature is present', function () {
  295. window.__initialData = {
  296. customerDomain: {
  297. subdomain: 'albertos-apples',
  298. organizationUrl: 'https://albertos-apples.sentry.io',
  299. sentryUrl: 'https://sentry.io',
  300. },
  301. features: ['organizations:customer-domains'],
  302. links: {
  303. organizationUrl: 'https://albertos-apples.sentry.io',
  304. regionUrl: 'https://eu.sentry.io',
  305. sentryUrl: 'https://sentry.io',
  306. },
  307. } as any;
  308. const organization = OrganizationFixture({
  309. slug: 'albertos-apples',
  310. features: [],
  311. });
  312. const params = {
  313. orgId: 'albertos-apples',
  314. };
  315. const {router} = initializeOrg({
  316. organization,
  317. router: {
  318. params,
  319. },
  320. });
  321. const WrappedComponent = withDomainRequired(MyComponent);
  322. render(
  323. <WrappedComponent
  324. router={router}
  325. location={router.location}
  326. params={params}
  327. routes={router.routes}
  328. routeParams={router.params}
  329. route={{}}
  330. />,
  331. {router}
  332. );
  333. expect(screen.getByText('Org slug: albertos-apples')).toBeInTheDocument();
  334. expect(window.location.replace).toHaveBeenCalledTimes(0);
  335. });
  336. });