withDomainRequired.spec.tsx 12 KB

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