소스 검색

feat(testing): Add support for e2e tests backed by MySQL/MariaDB

Relates to #207
Michael Bromley 6 년 전
부모
커밋
dbc591f341

+ 2 - 0
packages/testing/package.json

@@ -40,7 +40,9 @@
     "sql.js": "^1.0.0"
   },
   "devDependencies": {
+    "@types/mysql": "^2.15.8",
     "@vendure/core": "^0.7.0",
+    "mysql": "^2.17.1",
     "rimraf": "^3.0.0",
     "typescript": "^3.6.4"
   }

+ 0 - 3
packages/testing/src/data-population/clear-all-tables.ts

@@ -11,9 +11,6 @@ export async function clearAllTables(config: VendureConfig, logging = true) {
     config = await preBootstrapConfig(config);
     const entityIdStrategy = config.entityIdStrategy;
     const connection = await createConnection({ ...config.dbConnectionOptions });
-    if (logging) {
-        console.log('Clearing all tables...');
-    }
     try {
         await connection.synchronize(true);
     } catch (err) {

+ 0 - 2
packages/testing/src/data-population/populate-for-testing.ts

@@ -6,7 +6,6 @@ import fs from 'fs-extra';
 
 import { TestServerOptions } from '../types';
 
-import { clearAllTables } from './clear-all-tables';
 import { populateCustomers } from './populate-customers';
 
 // tslint:disable:no-floating-promises
@@ -23,7 +22,6 @@ export async function populateForTesting(
     const originalRequireVerification = config.authOptions.requireVerification;
     config.authOptions.requireVerification = false;
 
-    await clearAllTables(config, logging);
     const [app, worker] = await bootstrapFn(config);
 
     await populateInitialData(app, options.initialData, logging);

+ 89 - 14
packages/testing/src/test-server.ts

@@ -3,7 +3,9 @@ import { NestFactory } from '@nestjs/core';
 import { DefaultLogger, Logger, VendureConfig } from '@vendure/core';
 import { preBootstrapConfig } from '@vendure/core/dist/bootstrap';
 import fs from 'fs';
+import { Connection } from 'mysql';
 import path from 'path';
+import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions';
 import { SqljsConnectionOptions } from 'typeorm/driver/sqljs/SqljsConnectionOptions';
 
 import { populateForTesting } from './data-population/populate-for-testing';
@@ -31,16 +33,19 @@ export class TestServer {
      * is loaded so that the populate step can be skipped, which speeds up the tests significantly.
      */
     async init(options: TestServerOptions): Promise<void> {
-        const dbFilePath = this.getDbFilePath(options.dataDir);
-        (this.vendureConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).location = dbFilePath;
-        if (!fs.existsSync(dbFilePath)) {
-            if (options.logging) {
-                console.log(`Test data not found. Populating database and caching...`);
-            }
-            await this.populateInitialData(this.vendureConfig, options);
-        }
-        if (options.logging) {
-            console.log(`Loading test data from "${dbFilePath}"`);
+        const { type } = this.vendureConfig.dbConnectionOptions;
+        switch (type) {
+            case 'sqljs':
+                await this.initSqljs(options);
+                break;
+            case 'mysql':
+                await this.initMysql(
+                    this.vendureConfig.dbConnectionOptions as MysqlConnectionOptions,
+                    options,
+                );
+                break;
+            default:
+                throw new Error(`The TestServer does not support the database type "${type}"`);
         }
         const [app, worker] = await this.bootstrapForTesting(this.vendureConfig);
         if (app) {
@@ -68,15 +73,79 @@ export class TestServer {
         }
     }
 
+    private async initSqljs(options: TestServerOptions) {
+        const dbFilePath = this.getDbFilePath(options.dataDir);
+        (this.vendureConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).location = dbFilePath;
+        if (!fs.existsSync(dbFilePath)) {
+            if (options.logging) {
+                console.log(`Test data not found. Populating database and caching...`);
+            }
+            await this.populateInitialData(this.vendureConfig, options);
+        }
+        if (options.logging) {
+            console.log(`Loading test data from "${dbFilePath}"`);
+        }
+    }
+
+    private async initMysql(connectionOptions: MysqlConnectionOptions, options: TestServerOptions) {
+        const filename = this.getCallerFilename(2);
+        const conn = await this.getMysqlConnection(connectionOptions);
+        const dbName = 'e2e_' + path.basename(filename).replace(/[^a-z0-9_]/gi, '_');
+        (connectionOptions as any).database = dbName;
+        (connectionOptions as any).synchronize = true;
+        await new Promise((resolve, reject) => {
+            conn.query(`DROP DATABASE IF EXISTS ${dbName}`, err => {
+                if (err) {
+                    console.log(err);
+                    reject(err);
+                    return;
+                }
+                resolve();
+            });
+        });
+        await new Promise((resolve, reject) => {
+            conn.query(`CREATE DATABASE IF NOT EXISTS ${dbName}`, err => {
+                if (err) {
+                    console.log(err);
+                    reject(err);
+                    return;
+                }
+                resolve();
+            });
+        });
+        await this.populateInitialData(this.vendureConfig, options);
+        conn.destroy();
+    }
+
+    private async getMysqlConnection(connectionOptions: MysqlConnectionOptions): Promise<Connection> {
+        const { createConnection } = await import('mysql');
+        const conn = createConnection({
+            host: connectionOptions.host,
+            port: connectionOptions.port,
+            user: connectionOptions.username,
+            password: connectionOptions.password,
+        });
+        await new Promise((resolve, reject) => {
+            conn.connect(err => {
+                if (err) {
+                    reject(err);
+                    return;
+                }
+                resolve();
+            });
+        });
+        return conn;
+    }
+
     private getDbFilePath(dataDir: string) {
         // tslint:disable-next-line:no-non-null-assertion
-        const testFilePath = this.getCallerFilename(2);
+        const testFilePath = this.getCallerFilename(3);
         const dbFileName = path.basename(testFilePath) + '.sqlite';
         const dbFilePath = path.join(dataDir, dbFileName);
         return dbFilePath;
     }
 
-    private getCallerFilename(depth: number) {
+    private getCallerFilename(depth: number): string {
         let pst: ErrorConstructor['prepareStackTrace'];
         let stack: any;
         let file: any;
@@ -106,7 +175,11 @@ export class TestServer {
         testingConfig: Required<VendureConfig>,
         options: TestServerOptions,
     ): Promise<void> {
-        (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = true;
+        const isSqljs = testingConfig.dbConnectionOptions.type === 'sqljs';
+        if (isSqljs) {
+            (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = true;
+            (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).synchronize = true;
+        }
 
         const [app, worker] = await populateForTesting(testingConfig, this.bootstrapForTesting, {
             logging: false,
@@ -117,7 +190,9 @@ export class TestServer {
         }
         await app.close();
 
-        (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = false;
+        if (isSqljs) {
+            (testingConfig.dbConnectionOptions as Mutable<SqljsConnectionOptions>).autoSave = false;
+        }
     }
 
     /**

+ 8 - 1
yarn.lock

@@ -2295,6 +2295,13 @@
   resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
   integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
 
+"@types/mysql@^2.15.8":
+  version "2.15.8"
+  resolved "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.8.tgz#305172ee6ec530acd16a713f680dbfb4399960a8"
+  integrity sha512-l0TUdg6KDEaLO75/yjdjksobJDRWv8iZlpRfv/WW1lQZCQDKdTDnKCkeH10oapzP/JTuKiTy6Cvq/sm/0GgcUw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/nanoid@^2.1.0":
   version "2.1.0"
   resolved "https://registry.npmjs.org/@types/nanoid/-/nanoid-2.1.0.tgz#41edfda78986e9127d0dc14de982de766f994020"
@@ -10904,7 +10911,7 @@ mute-stream@0.0.8, mute-stream@~0.0.4:
   resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
   integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
 
-mysql@^2.16.0:
+mysql@^2.16.0, mysql@^2.17.1:
   version "2.17.1"
   resolved "https://registry.npmjs.org/mysql/-/mysql-2.17.1.tgz#62bba4a039a9b2f73638cd1652ce50fc6f682899"
   integrity sha512-7vMqHQ673SAk5C8fOzTG2LpPcf3bNt0oL3sFpxPEEFp1mdlDcrLK0On7z8ZYKaaHrHwNcQ/MTUz7/oobZ2OyyA==