nmig/test/TestModules/TestSchemaProcessor.ts
2018-09-19 16:56:41 +03:00

300 lines
10 KiB
TypeScript

/*
* This file is a part of "NMIG" - the database migration tool.
*
* Copyright (C) 2016 - present, Anatoly Khaytovich <anatolyuss@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (please see the "LICENSE.md" file).
* If not, see <http://www.gnu.org/licenses/gpl.txt>.
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
import * as path from 'path';
import * as fs from 'fs';
import { EventEmitter } from 'events';
import Conversion from '../../src/Conversion';
import DBAccess from '../../src/DBAccess';
import DBVendors from '../../src/DBVendors';
import DBAccessQueryResult from '../../src/DBAccessQueryResult';
import { Main } from '../../src/Main';
import SchemaProcessor from '../../src/SchemaProcessor';
import readDataTypesMap from '../../src/DataTypesMapReader';
import loadStructureToMigrate from '../../src/StructureLoader';
import pipeData from '../../src/DataPipeManager';
import { createStateLogsTable } from '../../src/MigrationStateManager';
import { createDataPoolTable, readDataPool } from '../../src/DataPoolManager';
import generateError from '../../src/ErrorGenerator';
import log from '../../src/Logger';
export default class TestSchemaProcessor {
/**
* Instance of class Main.
*/
private readonly _app: Main;
/**
* Instance of class Conversion.
*/
public conversion?: Conversion;
/**
* Instance of class DBAccess.
*/
public dbAccess?: DBAccess;
/**
* TestSchemaProcessor constructor.
*/
public constructor() {
this._app = new Main();
this.conversion = undefined;
this.dbAccess = undefined;
}
/**
* Stops the process in case of fatal error.
*/
public processFatalError(error: string): void {
console.log(error);
generateError(<Conversion>this.conversion, error);
process.exit();
}
/**
* Removes resources created by test scripts.
*/
public async removeTestResources(): Promise<void> {
if (!(<Conversion>this.conversion)._removeTestResources) {
return;
}
const sqlDropMySqlDatabase: string = `DROP DATABASE ${ (<Conversion>this.conversion)._mySqlDbName };`;
await (<DBAccess>this.dbAccess).query(
'removeTestResources',
sqlDropMySqlDatabase,
DBVendors.MYSQL,
true,
false
);
const sqlDropPgDatabase: string = `DROP SCHEMA ${ (<Conversion>this.conversion)._schema } CASCADE;`;
await (<DBAccess>this.dbAccess).query(
'removeTestResources',
sqlDropPgDatabase,
DBVendors.PG,
true,
false
);
}
/**
* Prevents tests from running if test dbs (both MySQL and PostgreSQL) already exist.
*/
private async _checkResources(conversion: Conversion): Promise<Conversion> {
const logTitle: string = 'TestSchemaProcessor::_checkResources';
const sqlIsMySqlDbExist: string = `SELECT EXISTS (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME = '${ (<Conversion>this.conversion)._mySqlDbName }') AS \`exists\`;`;
const mySqlResult: DBAccessQueryResult = await (<DBAccess>this.dbAccess).query(
logTitle,
sqlIsMySqlDbExist,
DBVendors.MYSQL,
true,
false
);
const mySqlExists: boolean = !!mySqlResult.data[0].exists;
const sqlIsPgDbExist: string = `SELECT EXISTS(SELECT schema_name FROM information_schema.schemata
WHERE schema_name = '${ (<Conversion>this.conversion)._schema }');`;
const pgResult: DBAccessQueryResult = await (<DBAccess>this.dbAccess).query(
logTitle,
sqlIsPgDbExist,
DBVendors.PG,
true,
false
);
const pgExists: boolean = !!pgResult.data.rows[0].exists;
let msg: string = '';
if (mySqlExists) {
msg += `Please, remove '${ (<Conversion>this.conversion)._mySqlDbName }' database from your MySQL server prior to running tests.\n`;
}
if (pgExists) {
const schemaName: string = `'${ (<Conversion>this.conversion)._targetConString.database }.${ (<Conversion>this.conversion)._schema }'`;
msg += `Please, remove ${ schemaName } schema from your PostgreSQL server prior to running tests.`;
}
if (msg) {
log(<Conversion>this.conversion, msg);
process.exit();
}
return conversion;
}
/**
* Creates test source database.
*/
private async _createTestSourceDb(conversion: Conversion): Promise<Conversion> {
const sql: string = `CREATE DATABASE IF NOT EXISTS ${ (<Conversion>this.conversion)._mySqlDbName };`;
await (<DBAccess>this.dbAccess).query('_createTestSourceDb', sql, DBVendors.MYSQL, true, false);
return conversion;
}
/**
* Updates the "database" part of MySQL connection.
*/
private _updateMySqlConnection(conversion: Conversion): Promise<Conversion> {
return new Promise<Conversion>(resolve => {
conversion._mysql = undefined;
conversion._sourceConString.database = conversion._mySqlDbName;
resolve(conversion);
});
}
/**
* Reads contents from the specified resource.
*/
private _readFile(filePath: string): Promise<Buffer> {
return new Promise<Buffer>(resolve => {
fs.readFile(filePath, (error: Error, data: Buffer) => {
if (error) {
console.log(`\t--[_readFile] Cannot read file from ${ filePath }`);
process.exit();
}
resolve(data);
});
});
}
/**
* Reads test schema sql file.
*/
private _readTestSchema(): Promise<Buffer> {
const testSchemaFilePath: string = path.join(__dirname, '..', '..', '..', 'test', 'test_schema.sql');
return this._readFile(testSchemaFilePath);
}
/**
* Loads test schema into MySQL test database.
*/
private async _loadTestSchema(conversion: Conversion): Promise<Conversion> {
const sqlBuffer: Buffer = await this._readTestSchema();
await (<DBAccess>this.dbAccess).query(
'_loadTestSchema',
sqlBuffer.toString(),
DBVendors.MYSQL,
true,
false
);
return conversion;
}
/**
* Provides a blob for a sake of testing.
*/
public getTestBlob(conversion: Conversion): Buffer {
return Buffer.from('Automated tests development is in progress.', conversion._encoding);
}
/**
* Loads test data into MySQL test database.
*/
private async _loadTestData(conversion: Conversion): Promise<Conversion> {
const insertParams: any = {
id_test_unique_index: 7384,
id_test_composite_unique_index_1: 125,
id_test_composite_unique_index_2: 234,
id_test_index: 123,
int_test_not_null: 123,
id_test_composite_index_1: 11,
id_test_composite_index_2: 22,
json_test_comment: '{"prop1":"First","prop2":2}',
bit: 1,
year: 1984,
bigint: '1234567890123456800',
float: 12345.5,
double: 123456789.23,
numeric: '1234567890',
decimal: '1234567890',
char_5: 'fghij',
varchar_5: 'abcde',
date: '1984-11-30',
time: '21:12:33',
timestamp: '2018-11-11 22:21:20',
enum: 'e1',
set: 's2',
text: 'Test text',
blob: this.getTestBlob(conversion)
};
const insertParamsKeys: string[] = Object.keys(insertParams).map((k: string) => `\`${ k }\``);
const sql: string = `INSERT INTO \`table_a\`(${ insertParamsKeys.join(',') }) VALUES(${ insertParamsKeys.map((k: string) => '?').join(',') });`;
await (<DBAccess>this.dbAccess).query(
'TestSchemaProcessor::_loadTestData',
sql,
DBVendors.MYSQL,
true,
false,
undefined,
Object.values(insertParams)
);
return conversion;
}
/**
* Initializes Conversion instance.
*/
public async initializeConversion(): Promise<Conversion> {
const baseDir: string = path.join(__dirname, '..', '..', '..');
const config: any = await this._app.readConfig(baseDir, 'test_config.json');
const fullConfig: any = await this._app.readExtraConfig(config, baseDir);
this.conversion = await this._app.initializeConversion(fullConfig);
this.conversion._runsInTestMode = true;
this.conversion._eventEmitter = new EventEmitter();
this.dbAccess = new DBAccess(this.conversion);
delete this.conversion._sourceConString.database;
return this.conversion;
}
/**
* Arranges test migration.
* "migrationCompleted" event will fire on completion.
*/
public arrangeTestMigration(conversion: Conversion): void {
Promise.resolve(conversion)
.then(this._checkResources.bind(this))
.then(this._createTestSourceDb.bind(this))
.then(this._updateMySqlConnection.bind(this))
.then(this._loadTestSchema.bind(this))
.then(this._loadTestData.bind(this))
.then(readDataTypesMap)
.then(this._app.createLogsDirectory)
.then(conversion => (new SchemaProcessor(conversion)).createSchema())
.then(createStateLogsTable)
.then(createDataPoolTable)
.then(loadStructureToMigrate)
.then(readDataPool)
.then(pipeData)
.catch(error => console.log(error));
}
}