routes.spec.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import {createRoutes, RouteComponent} from 'react-router';
  2. import * as constants from 'sentry/constants';
  3. import {buildRoutes} from 'sentry/routes';
  4. import {normalizeUrl} from './utils/withDomainRequired';
  5. // Setup a module mock so that we can replace
  6. // usingCustomerDomain with a getter.
  7. jest.mock('sentry/constants', () => {
  8. const originalModule = jest.requireActual('sentry/constants');
  9. return {
  10. __esModule: true,
  11. ...originalModule,
  12. get usingCustomerDomain() {
  13. return false;
  14. },
  15. };
  16. });
  17. // Workaround react-router PlainRoute type not covering redirect routes.
  18. type RouteShape = {
  19. childRoutes?: RouteShape[];
  20. component?: RouteComponent;
  21. from?: string;
  22. path?: string;
  23. };
  24. type RouteMetadata = {
  25. leadingPath: string;
  26. route: RouteShape;
  27. };
  28. function extractRoutes(rootRoute: any): Record<string, RouteComponent> {
  29. const routeTree = createRoutes(rootRoute);
  30. const routeMap: Record<string, RouteComponent> = {};
  31. // A queue of routes we need to visit
  32. const visitQueue: RouteMetadata[] = [{leadingPath: '', route: routeTree[0]}];
  33. while (visitQueue.length > 0) {
  34. const current = visitQueue.pop();
  35. if (!current) {
  36. break;
  37. }
  38. let leading = current.leadingPath;
  39. if (current.route.path?.startsWith('/')) {
  40. leading = '';
  41. }
  42. const currentPath = `${leading}${current.route.path ?? ''}`.replace('//', '/');
  43. if (current.route.childRoutes) {
  44. for (const childRoute of current.route.childRoutes ?? []) {
  45. visitQueue.push({
  46. leadingPath: currentPath,
  47. route: childRoute,
  48. });
  49. }
  50. } else {
  51. if (current.route.from) {
  52. // Redirect routes are not relevant to us.
  53. continue;
  54. }
  55. // We are on a terminal route in the tree. Add to the map of route components.
  56. // We are less interested in container route components.
  57. if (current.route.component) {
  58. routeMap[currentPath] = current.route.component;
  59. }
  60. }
  61. }
  62. return routeMap;
  63. }
  64. describe('buildRoutes()', function () {
  65. // Until customer-domains is mainlined and path
  66. // based slug routes are removed we need to ensure
  67. // that each orgId route also has slugless path.
  68. test('orgId routes also have domain routes', function () {
  69. const spy = jest.spyOn(constants, 'usingCustomerDomain', 'get');
  70. // Get routes for with customer domains off.
  71. spy.mockReturnValue(false);
  72. const routeMap = extractRoutes(buildRoutes());
  73. // Get routes with customer domains on.
  74. spy.mockReturnValue(true);
  75. const domainRoutes = extractRoutes(buildRoutes());
  76. // All routes that exist under orgId path slugs should
  77. // have a sibling under customer-domains.
  78. const mismatch: Array<{domain: string; slug: string}> = [];
  79. for (const path in routeMap) {
  80. // Normalize the URLs so that we know the path we're looking for.
  81. const domainPath = normalizeUrl(path, {forceCustomerDomain: true});
  82. // Path is not different under customer domains.
  83. if (domainPath === path) {
  84. continue;
  85. }
  86. if (!domainRoutes[domainPath]) {
  87. mismatch.push({slug: path, domain: domainPath});
  88. }
  89. }
  90. if (mismatch.length > 0) {
  91. const routelist = mismatch
  92. .map(item => `- slug: ${item.slug}\n domain: ${item.domain}`)
  93. .join('\n');
  94. throw new Error(
  95. `Unable to find matching URLs for the following ${mismatch.length} routes:\n\n` +
  96. routelist +
  97. '\n\nEach route with the :orgId parameter is expected to have corresponding domain based route as well. ' +
  98. 'If you need help with this drop by #proj-hybrid-cloud.'
  99. );
  100. }
  101. });
  102. });