test-server.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { INestApplication, INestMicroservice } from '@nestjs/common';
  2. import { NestFactory } from '@nestjs/core';
  3. import { Omit } from '@vendure/common/lib/omit';
  4. import fs from 'fs';
  5. import path from 'path';
  6. import { ConnectionOptions } from 'typeorm';
  7. import { SqljsConnectionOptions } from 'typeorm/driver/sqljs/SqljsConnectionOptions';
  8. import { populateForTesting, PopulateOptions } from '../mock-data/populate-for-testing';
  9. import { preBootstrapConfig } from '../src/bootstrap';
  10. import { Mutable } from '../src/common/types/common-types';
  11. import { DefaultLogger } from '../src/config/logger/default-logger';
  12. import { Logger } from '../src/config/logger/vendure-logger';
  13. import { VendureConfig } from '../src/config/vendure-config';
  14. import { testConfig } from './config/test-config';
  15. import { setTestEnvironment } from './utils/test-environment';
  16. // tslint:disable:no-console
  17. /**
  18. * A server against which the e2e tests should be run.
  19. */
  20. export class TestServer {
  21. app: INestApplication;
  22. worker?: INestMicroservice;
  23. /**
  24. * Bootstraps an instance of Vendure server and populates the database according to the options
  25. * passed in. Should be called immediately after creating the client in the `beforeAll` function.
  26. *
  27. * The populated data is saved into an .sqlite file for each test file. On subsequent runs, this file
  28. * is loaded so that the populate step can be skipped, which speeds up the tests significantly.
  29. */
  30. async init(
  31. options: Omit<PopulateOptions, 'initialDataPath'>,
  32. customConfig: Partial<VendureConfig> = {},
  33. ): Promise<void> {
  34. setTestEnvironment();
  35. const testingConfig = { ...testConfig, ...customConfig };
  36. const dbFilePath = this.getDbFilePath();
  37. (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).location = dbFilePath;
  38. if (!fs.existsSync(dbFilePath)) {
  39. if (options.logging) {
  40. console.log(`Test data not found. Populating database and caching...`);
  41. }
  42. await this.populateInitialData(testingConfig, options);
  43. }
  44. if (options.logging) {
  45. console.log(`Loading test data from "${dbFilePath}"`);
  46. }
  47. const [app, worker] = await this.bootstrapForTesting(testingConfig);
  48. if (app) {
  49. this.app = app;
  50. } else {
  51. console.error(`Could not bootstrap app`);
  52. process.exit(1);
  53. }
  54. if (worker) {
  55. this.worker = worker;
  56. }
  57. }
  58. /**
  59. * Destroy the Vendure instance. Should be called in the `afterAll` function.
  60. */
  61. async destroy() {
  62. // allow a grace period of any outstanding async tasks to complete
  63. await new Promise(resolve => global.setTimeout(resolve, 500));
  64. await this.app.close();
  65. if (this.worker) {
  66. await this.worker.close();
  67. }
  68. }
  69. private getDbFilePath() {
  70. const dbDataDir = '__data__';
  71. // tslint:disable-next-line:no-non-null-assertion
  72. const testFilePath = module!.parent!.filename;
  73. const dbFileName = path.basename(testFilePath) + '.sqlite';
  74. const dbFilePath = path.join(path.dirname(testFilePath), dbDataDir, dbFileName);
  75. return dbFilePath;
  76. }
  77. /**
  78. * Populates an .sqlite database file based on the PopulateOptions.
  79. */
  80. private async populateInitialData(
  81. testingConfig: VendureConfig,
  82. options: Omit<PopulateOptions, 'initialDataPath'>,
  83. ): Promise<void> {
  84. (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = true;
  85. const [app, worker] = await populateForTesting(testingConfig, this.bootstrapForTesting, {
  86. logging: false,
  87. ...{
  88. ...options,
  89. initialDataPath: path.join(__dirname, 'fixtures/e2e-initial-data.ts'),
  90. },
  91. });
  92. await app.close();
  93. if (worker) {
  94. await worker.close();
  95. }
  96. (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = false;
  97. }
  98. /**
  99. * Bootstraps an instance of the Vendure server for testing against.
  100. */
  101. private async bootstrapForTesting(
  102. userConfig: Partial<VendureConfig>,
  103. ): Promise<[INestApplication, INestMicroservice | undefined]> {
  104. const config = await preBootstrapConfig(userConfig);
  105. Logger.useLogger(config.logger);
  106. const appModule = await import('../src/app.module');
  107. try {
  108. DefaultLogger.hideNestBoostrapLogs();
  109. const app = await NestFactory.create(appModule.AppModule, { cors: config.cors, logger: false });
  110. let worker: INestMicroservice | undefined;
  111. await app.listen(config.port);
  112. if (config.workerOptions.runInMainProcess) {
  113. const workerModule = await import('../src/worker/worker.module');
  114. worker = await NestFactory.createMicroservice(workerModule.WorkerModule, {
  115. transport: config.workerOptions.transport,
  116. logger: new Logger(),
  117. options: config.workerOptions.options,
  118. });
  119. await worker.listenAsync();
  120. }
  121. DefaultLogger.restoreOriginalLogLevel();
  122. return [app, worker];
  123. } catch (e) {
  124. console.log(e);
  125. throw e;
  126. }
  127. }
  128. }