test-server.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import { INestApplication } from '@nestjs/common';
  2. import { NestFactory } from '@nestjs/core';
  3. import { DefaultLogger, JobQueueService, Logger, VendureConfig } from '@vendure/core';
  4. import { preBootstrapConfig } from '@vendure/core/dist/bootstrap';
  5. import cookieSession from 'cookie-session';
  6. import { populateForTesting } from './data-population/populate-for-testing';
  7. import { getInitializerFor } from './initializers/initializers';
  8. import { TestServerOptions } from './types';
  9. /* eslint-disable no-console */
  10. /**
  11. * @description
  12. * A real Vendure server against which the e2e tests should be run.
  13. *
  14. * @docsCategory testing
  15. */
  16. export class TestServer {
  17. public app: INestApplication;
  18. constructor(private vendureConfig: Required<VendureConfig>) {}
  19. /**
  20. * @description
  21. * Bootstraps an instance of Vendure server and populates the database according to the options
  22. * passed in. Should be called in the `beforeAll` function.
  23. *
  24. * The populated data is saved into an .sqlite file for each test file. On subsequent runs, this file
  25. * is loaded so that the populate step can be skipped, which speeds up the tests significantly.
  26. */
  27. async init(options: TestServerOptions): Promise<void> {
  28. const { type } = this.vendureConfig.dbConnectionOptions;
  29. const { dbConnectionOptions } = this.vendureConfig;
  30. const testFilename = this.getCallerFilename(1);
  31. const initializer = getInitializerFor(type);
  32. try {
  33. await initializer.init(testFilename, dbConnectionOptions);
  34. const populateFn = () => this.populateInitialData(this.vendureConfig, options);
  35. await initializer.populate(populateFn);
  36. await initializer.destroy();
  37. } catch (e: any) {
  38. throw e;
  39. }
  40. await this.bootstrap();
  41. }
  42. /**
  43. * @description
  44. * Bootstraps a Vendure server instance. Generally the `.init()` method should be used, as that will also
  45. * populate the test data. However, the `bootstrap()` method is sometimes useful in tests which need to
  46. * start and stop a Vendure instance multiple times without re-populating data.
  47. */
  48. async bootstrap() {
  49. this.app = await this.bootstrapForTesting(this.vendureConfig);
  50. }
  51. /**
  52. * @description
  53. * Destroy the Vendure server instance and clean up all resources.
  54. * Should be called after all tests have run, e.g. in an `afterAll` function.
  55. */
  56. async destroy() {
  57. // allow a grace period of any outstanding async tasks to complete
  58. await new Promise(resolve => global.setTimeout(resolve, 500));
  59. await this.app?.close();
  60. }
  61. private getCallerFilename(depth: number): string {
  62. let stack: any;
  63. let file: any;
  64. let frame: any;
  65. const pst = Error.prepareStackTrace;
  66. Error.prepareStackTrace = (_, _stack) => {
  67. Error.prepareStackTrace = pst;
  68. return _stack;
  69. };
  70. stack = new Error().stack;
  71. stack = stack.slice(depth + 1);
  72. do {
  73. frame = stack.shift();
  74. file = frame && frame.getFileName();
  75. } while (stack.length && file === 'module.js');
  76. return file;
  77. }
  78. /**
  79. * Populates an .sqlite database file based on the PopulateOptions.
  80. */
  81. private async populateInitialData(
  82. testingConfig: Required<VendureConfig>,
  83. options: TestServerOptions,
  84. ): Promise<void> {
  85. const app = await populateForTesting(testingConfig, this.bootstrapForTesting, {
  86. logging: false,
  87. ...options,
  88. });
  89. await app.close();
  90. }
  91. /**
  92. * Bootstraps an instance of the Vendure server for testing against.
  93. */
  94. private async bootstrapForTesting(
  95. this: void,
  96. userConfig: Partial<VendureConfig>,
  97. ): Promise<INestApplication> {
  98. const config = await preBootstrapConfig(userConfig);
  99. Logger.useLogger(config.logger);
  100. const appModule = await import('@vendure/core/dist/app.module');
  101. try {
  102. DefaultLogger.hideNestBoostrapLogs();
  103. const app = await NestFactory.create(appModule.AppModule, {
  104. cors: config.apiOptions.cors,
  105. logger: new Logger(),
  106. });
  107. const { tokenMethod } = config.authOptions;
  108. const usingCookie =
  109. tokenMethod === 'cookie' || (Array.isArray(tokenMethod) && tokenMethod.includes('cookie'));
  110. if (usingCookie) {
  111. const { cookieOptions } = config.authOptions;
  112. app.use(cookieSession(cookieOptions));
  113. }
  114. const earlyMiddlewares = config.apiOptions.middleware.filter(mid => mid.beforeListen);
  115. earlyMiddlewares.forEach(mid => {
  116. app.use(mid.route, mid.handler);
  117. });
  118. await app.listen(config.apiOptions.port);
  119. await app.get(JobQueueService).start();
  120. DefaultLogger.restoreOriginalLogLevel();
  121. return app;
  122. } catch (e: any) {
  123. console.log(e);
  124. throw e;
  125. }
  126. }
  127. }