test-server.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { INestApplication, INestMicroservice } from '@nestjs/common';
  2. import { NestFactory } from '@nestjs/core';
  3. import { DefaultLogger, Logger, VendureConfig } from '@vendure/core';
  4. import { preBootstrapConfig } from '@vendure/core/dist/bootstrap';
  5. import { populateForTesting } from './data-population/populate-for-testing';
  6. import { getInitializerFor } from './initializers/initializers';
  7. import { TestServerOptions } from './types';
  8. // tslint:disable:no-console
  9. /**
  10. * @description
  11. * A real Vendure server against which the e2e tests should be run.
  12. *
  13. * @docsCategory testing
  14. */
  15. export class TestServer {
  16. private app: INestApplication;
  17. private worker?: INestMicroservice;
  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) {
  38. console.error(e);
  39. process.exit(1);
  40. }
  41. await this.bootstrap();
  42. }
  43. /**
  44. * @description
  45. * Bootstraps a Vendure server instance. Generally the `.init()` method should be used, as that will also
  46. * populate the test data. However, the `bootstrap()` method is sometimes useful in tests which need to
  47. * start and stop a Vendure instance multiple times without re-populating data.
  48. */
  49. async bootstrap() {
  50. const [app, worker] = await this.bootstrapForTesting(this.vendureConfig);
  51. if (app) {
  52. this.app = app;
  53. } else {
  54. console.error(`Could not bootstrap app`);
  55. process.exit(1);
  56. }
  57. if (worker) {
  58. this.worker = worker;
  59. }
  60. }
  61. /**
  62. * @description
  63. * Destroy the Vendure server instance and clean up all resources.
  64. * Should be called after all tests have run, e.g. in an `afterAll` function.
  65. */
  66. async destroy() {
  67. // allow a grace period of any outstanding async tasks to complete
  68. await new Promise(resolve => global.setTimeout(resolve, 500));
  69. await this.app.close();
  70. if (this.worker) {
  71. await this.worker.close();
  72. }
  73. }
  74. private getCallerFilename(depth: number): string {
  75. let pst: ErrorConstructor['prepareStackTrace'];
  76. let stack: any;
  77. let file: any;
  78. let frame: any;
  79. pst = Error.prepareStackTrace;
  80. Error.prepareStackTrace = (_, _stack) => {
  81. Error.prepareStackTrace = pst;
  82. return _stack;
  83. };
  84. stack = new Error().stack;
  85. stack = stack.slice(depth + 1);
  86. do {
  87. frame = stack.shift();
  88. file = frame && frame.getFileName();
  89. } while (stack.length && file === 'module.js');
  90. return file;
  91. }
  92. /**
  93. * Populates an .sqlite database file based on the PopulateOptions.
  94. */
  95. private async populateInitialData(
  96. testingConfig: Required<VendureConfig>,
  97. options: TestServerOptions,
  98. ): Promise<void> {
  99. const [app, worker] = await populateForTesting(testingConfig, this.bootstrapForTesting, {
  100. logging: false,
  101. ...options,
  102. });
  103. if (worker) {
  104. await worker.close();
  105. }
  106. await app.close();
  107. }
  108. /**
  109. * Bootstraps an instance of the Vendure server for testing against.
  110. */
  111. private async bootstrapForTesting(
  112. userConfig: Partial<VendureConfig>,
  113. ): Promise<[INestApplication, INestMicroservice | undefined]> {
  114. const config = await preBootstrapConfig(userConfig);
  115. Logger.useLogger(config.logger);
  116. const appModule = await import('@vendure/core/dist/app.module');
  117. try {
  118. DefaultLogger.hideNestBoostrapLogs();
  119. const app = await NestFactory.create(appModule.AppModule, {
  120. cors: config.apiOptions.cors,
  121. logger: new Logger(),
  122. });
  123. let worker: INestMicroservice | undefined;
  124. await app.listen(config.apiOptions.port);
  125. if (config.workerOptions.runInMainProcess) {
  126. const workerModule = await import('@vendure/core/dist/worker/worker.module');
  127. worker = await NestFactory.createMicroservice(workerModule.WorkerModule, {
  128. transport: config.workerOptions.transport,
  129. logger: new Logger(),
  130. options: config.workerOptions.options,
  131. });
  132. await worker.listenAsync();
  133. }
  134. DefaultLogger.restoreOriginalLogLevel();
  135. return [app, worker];
  136. } catch (e) {
  137. console.log(e);
  138. throw e;
  139. }
  140. }
  141. }