loadFixtures.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /* global __dirname */
  2. /* eslint import/no-nodejs-modules:0 */
  3. import fs from 'fs';
  4. import path from 'path';
  5. import TestStubFixtures from '../../../fixtures/js-stubs/types';
  6. const FIXTURES_ROOT = path.join(__dirname, '../../../fixtures');
  7. type Options = {
  8. /**
  9. * Flatten all fixtures to together into a single object
  10. */
  11. flatten?: boolean;
  12. };
  13. /**
  14. * Loads a directory of fixtures. Supports js and json fixtures.
  15. */
  16. export function loadFixtures(dir: string, opts: Options = {}): TestStubFixtures {
  17. const from = path.join(FIXTURES_ROOT, dir);
  18. const files = fs.readdirSync(from);
  19. // @ts-expect-error, this is a partial definition
  20. const fixtures: TestStubFixtures = {};
  21. for (const file of files) {
  22. const filePath = path.join(from, file);
  23. if (/[jt]sx?$/.test(file)) {
  24. const module = require(filePath);
  25. if (module.default) {
  26. throw new Error('Javascript fixtures cannot use default export');
  27. }
  28. fixtures[file] = module;
  29. continue;
  30. }
  31. if (/json$/.test(file)) {
  32. fixtures[file] = JSON.parse(fs.readFileSync(filePath).toString());
  33. continue;
  34. }
  35. throw new Error(`Invalid fixture type found: ${file}`);
  36. }
  37. if (opts.flatten) {
  38. // @ts-expect-error, this is a partial definition
  39. const flattenedFixtures: TestStubFixtures = {};
  40. for (const moduleKey in fixtures) {
  41. for (const moduleExport in fixtures[moduleKey]) {
  42. // Check if our flattenedFixtures already contains a key with the same export.
  43. // If it does, we want to throw and make sure that we dont silently override the fixtures.
  44. if (flattenedFixtures?.[moduleKey]?.[moduleExport]) {
  45. throw new Error(
  46. `Flatten will override module ${flattenedFixtures[moduleKey]} with ${fixtures[moduleKey][moduleExport]}`
  47. );
  48. }
  49. flattenedFixtures[moduleExport] = fixtures[moduleKey][moduleExport];
  50. }
  51. }
  52. return flattenedFixtures;
  53. }
  54. return fixtures;
  55. }
  56. const extensions = ['.js', '.ts', '.tsx', '.json'];
  57. // This is a mapping of special cases where fixture name does not map 1:1 to file name.
  58. // Some fixture files also contain more than one fixture so additional mappings are needed.
  59. // If you have added new fixtures and you are seeing an error being throw, please add the fixture
  60. const SPECIAL_MAPPING = {
  61. AllAuthenticators: 'authenticators.js',
  62. OrgRoleList: 'roleList.js',
  63. MetricsField: 'metrics.js',
  64. EventsStats: 'events.js',
  65. DetailedEvents: 'events.js',
  66. Events: 'events.js',
  67. OutcomesWithReason: 'outcomes.js',
  68. SentryAppComponentAsync: 'sentryAppComponent.js',
  69. EventStacktraceMessage: 'eventStacktraceException.js',
  70. MetricsTotalCountByReleaseIn24h: 'metrics.js',
  71. MetricsSessionUserCountByStatusByRelease: 'metrics.js',
  72. MOCK_RESP_VERBOSE: 'ruleConditions.js',
  73. SessionStatusCountByProjectInPeriod: 'sessions.js',
  74. SessionUserCountByStatusByRelease: 'sessions.js',
  75. SessionUserCountByStatus: 'sessions.js',
  76. SessionStatusCountByReleaseInPeriod: 'sessions.js',
  77. SessionsField: 'sessions.js',
  78. ProviderList: 'integrationListDirectory.js',
  79. BitbucketIntegrationConfig: 'integrationListDirectory.js',
  80. GitHubIntegration: 'githubIntegration.js',
  81. GitHubRepositoryProvider: 'githubRepositoryProvider.js',
  82. GitHubIntegrationProvider: 'githubIntegrationProvider.js',
  83. GitHubIntegrationConfig: 'integrationListDirectory.js',
  84. OrgOwnedApps: 'integrationListDirectory.js',
  85. PublishedApps: 'integrationListDirectory.js',
  86. SentryAppInstalls: 'integrationListDirectory.js',
  87. PluginListConfig: 'integrationListDirectory.js',
  88. DiscoverSavedQuery: 'discover.js',
  89. VercelProvider: 'vercelIntegration.js',
  90. TagValues: 'tagvalues.js',
  91. ReplayRRWebDivHelloWorld: 'replaySegments.ts',
  92. ReplayRRWebNode: 'replaySegments.ts',
  93. ReplaySegmentBreadcrumb: 'replaySegments.ts',
  94. ReplaySegmentConsole: 'replaySegments.ts',
  95. ReplaySegmentFullsnapshot: 'replaySegments.ts',
  96. ReplaySegmentInit: 'replaySegments.ts',
  97. ReplaySegmentNavigation: 'replaySegments.ts',
  98. ReplaySegmentSpan: 'replaySegments.ts',
  99. ReplaySpanPayload: 'replaySegments.ts',
  100. ReplaySpanPayloadNavigate: 'replaySegments.ts',
  101. };
  102. function tryRequire(dir: string, name: string): any {
  103. if (SPECIAL_MAPPING[name]) {
  104. return require(path.resolve(dir, SPECIAL_MAPPING[name]));
  105. }
  106. for (const ext of extensions) {
  107. try {
  108. return require(path.resolve(dir, lowercaseFirst(name) + ext));
  109. } catch {
  110. // ignore
  111. }
  112. }
  113. throw new Error('Failed to resolve file');
  114. }
  115. function lowercaseFirst(value: string): string {
  116. return value.charAt(0).toLowerCase() + value.slice(1);
  117. }
  118. export function makeLazyFixtures<UserProvidedFixtures extends Record<any, any>>(
  119. fixturesDirectoryPath: string,
  120. userProvidedFixtures: UserProvidedFixtures
  121. ): TestStubFixtures & UserProvidedFixtures {
  122. const lazyFixtures = new Proxy(
  123. {},
  124. {
  125. get(target, prop: string) {
  126. if (target[prop]) {
  127. return target[prop];
  128. }
  129. if (userProvidedFixtures[prop]) {
  130. return userProvidedFixtures[prop];
  131. }
  132. try {
  133. const maybeModule = tryRequire(fixturesDirectoryPath, prop);
  134. for (const exportKey in maybeModule) {
  135. target[exportKey] = maybeModule[exportKey];
  136. }
  137. } catch (error) {
  138. return () => {
  139. throw new Error(
  140. error +
  141. '\n\n' +
  142. `Failed to resolve ${prop} fixture.
  143. - Your fixture does not map directly to file on disk or fixture file could be exporting > 1 fixture.
  144. - To resolve this, add a mapping to SPECIAL_MAPPING in loadFixtures.ts or ensure fixture export name maps to the file on disk.
  145. - If you are seeing this only in CI and you have followed the step above, check the exact casing of the file as it is case sensitive.
  146. `
  147. );
  148. };
  149. }
  150. if (target[prop] === undefined) {
  151. return () => {
  152. throw new Error(
  153. `Failed to resolve ${prop} fixture.
  154. - Your fixture does not map directly to file on disk or fixture file could be exporting > 1 fixture.
  155. - To resolve this, add a mapping to SPECIAL_MAPPING in loadFixtures.ts or ensure fixture export name maps to the file on disk.
  156. - If you are seeing this only in CI and you have followed the step above, check the exact casing of the file as it is case sensitive.`
  157. );
  158. };
  159. }
  160. return target[prop];
  161. },
  162. }
  163. );
  164. return lazyFixtures as TestStubFixtures & UserProvidedFixtures;
  165. }