test-server.ts 5.6 KB

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