"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestingHelpers = exports.TestingFacade = exports.__ΩITestingFacadeOptions = exports.__ΩTestDbAdapter = void 0;
const __ΩRecord = ['K', 'T', 'Record', 'l\'e#"Rb!b"Pde"!N#!w#y'];
const __ΩOmit = ['T', 'K', () => __ΩPick, () => __ΩExclude, 'Omit', 'b!b"e!!e!!ge!"o$#o##w%y'];
const __ΩPick = ['T', 'K', 'Pick', 'l+e#!e"!fRb!b"Pde""N#!w#y'];
const __ΩExclude = ['T', 'U', 'Exclude', 'l6!Re$!RPe#!e$"qk#%QRb!b"Pde"!p)w#y'];
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
/*@ts-ignore*/
var { __ΩRootModuleDefinition } = require('@deepkit/app');
const app_1 = require("@deepkit/app");
/*@ts-ignore*/
var { __ΩClassType } = require('@deepkit/core');
const framework_1 = require("@deepkit/framework");
const mysql_1 = require("@deepkit/mysql");
const orm_1 = require("@deepkit/orm");
const postgres_1 = require("@deepkit/postgres");
/*@ts-ignore*/
var { __ΩCreateAppOptions } = require('../app');
const app_2 = require("../app");
const state_1 = require("../app/state");
const database_1 = require("../database");
const helpers_1 = require("../helpers");
const fixtures_1 = require("./fixtures");
const requests_1 = require("./requests");
const sql_1 = require("./sql");
process.env.TEST_RUN_TS ??= String(Math.floor(Date.now() / 1000));
let nextPoolId = 1;
const __ΩTestDbAdapter = ["mysql", "postgres", 'TestDbAdapter', 'P.!."Jw#y'];
exports.__ΩTestDbAdapter = __ΩTestDbAdapter;
function getTestDbAdapter() {
    if (process.env.DB_ADAPTER === 'postgres' || process.env.DB_ADAPTER === 'mysql') {
        return process.env.DB_ADAPTER;
    }
    if (process.env.PG_HOST)
        return 'postgres';
    return 'mysql';
}
getTestDbAdapter.__type = [() => __ΩTestDbAdapter, 'getTestDbAdapter', 'Pn!/"'];
const __ΩITestingFacadeOptions = ['enableDatabase', 'enableMigrations', 'autoSeedData', 'databasePrefix', () => __ΩTestDbAdapter, 'dbAdapter', () => TestingFacade, 'facade', '', 'onBeforeStart', () => TestingFacade, 'onStart', () => TestingFacade, 'onBeforeStop', () => TestingFacade, 'onStop', () => TestingFacade, 'seedData', () => __ΩRecord, 'defaultTestHeaders', 'ITestingFacadeOptions', 'P)4!8)4"8)4#8&4$8n%4&8PP7\'2($`/)4*8PP7+2($`/)4,8PP7-2($`/)4.8PP7/2($`/)408PP712($`/)428&&o3#448Mw5y'];
exports.__ΩITestingFacadeOptions = __ΩITestingFacadeOptions;
class TestingFacade extends framework_1.TestingFacade {
    constructor(app, options = {}) {
        super(app);
        this.options = options;
        this.sql = new sql_1.SqlTestingHelper();
        this.startTs = 0;
        this.stopped = false;
        this.dbAdapter = options.dbAdapter ?? getTestDbAdapter();
    }
    async start() {
        if (this.options?.enableDatabase) {
            await this.createDatabase();
        }
        await this.options?.onBeforeStart?.(this);
        if (this.databaseName) {
            if (this.dbAdapter === 'postgres') {
                process.env.PG_DATABASE = this.databaseName;
            }
            else {
                process.env.MYSQL_DATABASE = this.databaseName;
            }
        }
        // Ensure globalState points to this app before starting the server.
        // When multiple test facades are created (e.g. forEachAdapter), the last
        // createApp() call wins. Without this, the DB constructor's getAppConfig()
        // would resolve against the wrong app, eagerly loading its config from
        // stale env vars.
        state_1.globalState.currentApp = this.app;
        await this.startServer();
        try {
            const appServer = this.app.get(framework_1.ApplicationServer);
            const worker = appServer.getWorker();
            this.httpServer = worker['server'];
            this.httpServer.on('listening', () => {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                process.stdout.write(`HTTP listening at http://127.0.0.1:${this.httpServer.address().port}.\n`);
            });
            this.startTs = Date.now();
            await this.options?.onStart?.(this);
            if (this.options?.enableDatabase) {
                if (this.options?.enableMigrations !== false) {
                    await this.runMigrations();
                    await this.truncateTables();
                }
                if (this.options?.autoSeedData) {
                    await this.options?.seedData?.(this);
                }
            }
        }
        catch (err) {
            await this.stop();
            throw err;
        }
    }
    async stop() {
        if (this.stopped)
            return;
        this.stopped = true;
        await this.options?.onBeforeStop?.(this);
        // if the server is running for too short of a time, the database handshake
        // gets interrupted and the client hangs due to poor behaivor. thus,
        // we ensure we wait a minimum duration prior to telling the server to shut down.
        const durationMs = Date.now() - this.startTs;
        const waitMs = Math.max(0, 500 - durationMs);
        if (waitMs)
            await (0, helpers_1.sleepMs)(waitMs);
        await this.stopServer(true);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.httpServer?.closeAllConnections?.();
        if (this.options?.enableDatabase) {
            try {
                this.app.get(orm_1.DatabaseRegistry).onShutDown();
            }
            catch {
                // pool may already be closed if start() failed partway through
            }
        }
        await this.options?.onStop?.(this);
    }
    /**
     * ADDITIONS
     */
    async createDatabase() {
        const ts = process.env.TEST_RUN_TS ?? Math.floor(Date.now() / 1000);
        const poolId = nextPoolId++;
        this.databaseName = `${this.options?.databasePrefix ?? 'test'}_${ts}_${process.pid}_${poolId}`;
        if (this.dbAdapter === 'postgres') {
            const pgAdapter = new postgres_1.PostgresDatabaseAdapter({
                host: process.env.PG_HOST,
                port: process.env.PG_PORT ? parseInt(process.env.PG_PORT) : 5432,
                user: process.env.PG_USER,
                password: process.env.PG_PASSWORD_SECRET,
                database: 'postgres'
            });
            const pgConn = await pgAdapter.connectionPool.getConnection();
            await pgConn.run(`CREATE DATABASE "${this.databaseName}"`);
            await pgConn.release();
            await pgAdapter.disconnect();
        }
        else {
            const dbAdapter = new mysql_1.MySQLDatabaseAdapter({
                host: process.env.MYSQL_HOST,
                port: process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT) : 3306,
                user: process.env.MYSQL_USER,
                password: process.env.MYSQL_PASSWORD_SECRET,
                database: 'mysql',
                allowPublicKeyRetrieval: true,
                trace: true
            });
            const dbConn = await dbAdapter.connectionPool.getConnection();
            await dbConn.run(`CREATE DATABASE IF NOT EXISTS ${this.databaseName}`);
            await dbConn.release();
            await dbAdapter.disconnect();
        }
    }
    async destroyDatabase() {
        if (!this.databaseName || process.env.TEST_KEEP_DB)
            return;
        if (this.dbAdapter === 'postgres') {
            const pgAdapter = new postgres_1.PostgresDatabaseAdapter({
                host: process.env.PG_HOST,
                port: process.env.PG_PORT ? parseInt(process.env.PG_PORT) : 5432,
                user: process.env.PG_USER,
                password: process.env.PG_PASSWORD_SECRET,
                database: 'postgres'
            });
            const pgConn = await pgAdapter.connectionPool.getConnection();
            await pgConn.run(`DROP DATABASE IF EXISTS "${this.databaseName}"`);
            await pgConn.release();
            await pgAdapter.disconnect();
        }
        else {
            const dbAdapter = new mysql_1.MySQLDatabaseAdapter({
                host: process.env.MYSQL_HOST,
                port: process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT) : 3306,
                user: process.env.MYSQL_USER,
                password: process.env.MYSQL_PASSWORD_SECRET,
                database: 'mysql',
                allowPublicKeyRetrieval: true,
                trace: true
            });
            const dbConn = await dbAdapter.connectionPool.getConnection();
            await dbConn.run(`DROP DATABASE IF EXISTS ${this.databaseName}`);
            await dbConn.release();
            await dbAdapter.disconnect();
        }
    }
    async truncateTables() {
        if (this.dbAdapter === 'postgres') {
            const pgAdapter = new postgres_1.PostgresDatabaseAdapter({
                host: process.env.PG_HOST,
                port: process.env.PG_PORT ? parseInt(process.env.PG_PORT) : 5432,
                user: process.env.PG_USER,
                password: process.env.PG_PASSWORD_SECRET,
                database: this.databaseName
            });
            const pgConn = await pgAdapter.connectionPool.getConnection();
            try {
                const result = await pgConn.execAndReturnAll(`SELECT tablename FROM pg_tables WHERE schemaname = 'public'`);
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const tables = result.map(__assignType((row) => row.tablename, ['row', '', 'P"2!"/"']));
                for (const table of tables) {
                    if (table === '_migrations')
                        continue;
                    await pgConn.run(`TRUNCATE TABLE "${table}" CASCADE`);
                }
            }
            finally {
                await pgConn.release();
                await pgAdapter.disconnect();
            }
        }
        else {
            const dbAdapter = new mysql_1.MySQLDatabaseAdapter({
                host: process.env.MYSQL_HOST,
                port: process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT) : 3306,
                user: process.env.MYSQL_USER,
                password: process.env.MYSQL_PASSWORD_SECRET,
                database: this.databaseName,
                allowPublicKeyRetrieval: true,
                trace: true
            });
            const dbConn = await dbAdapter.connectionPool.getConnection();
            try {
                const result = await dbConn.execAndReturnAll('SHOW TABLES');
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const tables = result.map(__assignType((row) => Object.values(row)[0], ['row', '', 'P"2!"/"']));
                for (const table of tables) {
                    if (table === '_migrations')
                        continue;
                    await dbConn.run(`TRUNCATE TABLE ${table}`);
                }
            }
            finally {
                await dbConn.release();
                await dbAdapter.disconnect();
            }
        }
    }
    async runMigrations() {
        await (0, database_1.runMigrations)();
    }
    async resetToSeed() {
        if (!this.options?.enableDatabase)
            return;
        await this.truncateTables();
        await this.options?.seedData?.(this);
    }
}
exports.TestingFacade = TestingFacade;
TestingFacade.__type = [() => __ΩRootModuleDefinition, 'A', () => app_1.App, () => framework_1.TestingFacade, 'sql', function () { return new sql_1.SqlTestingHelper(); }, 'startTs', function () { return 0; }, 'databaseName', () => __ΩTestDbAdapter, 'dbAdapter', 'stopped', function () { return false; }, 'httpServer', () => app_1.App, 'app', () => __ΩITestingFacadeOptions, 'options', () => ({}), 'constructor', 'start', 'stop', 'createDatabase', 'destroyDatabase', 'truncateTables', 'runMigrations', 'resetToSeed', () => app_1.App, 'TestingFacade', 'n!c"PPe#!7#7$!3%>&\'3\'>(&3)8n*3+)3,;>-!3.8;PPe#!7/20n122:9>3"04P"05P"06P"07P"08P"09P"0:P"0;5Pe"!7<6"w='];
const __ΩTestingAppOptions = ['C', () => __ΩOmit, () => __ΩCreateAppOptions, "config", () => __ΩClassType, 'config', 'TestingAppOptions', 'b!P"o#".$o"#Pe#!o%"4&8MKw\'y'];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createTestingFacade(appOptions, options) {
    const adapter = getTestDbAdapter();
    if (!options?.enableDatabase) {
        if (adapter === 'mysql') {
            process.env.MYSQL_MIN_IDLE_CONNECTIONS = '0';
        }
    }
    const app = (0, app_2.createApp)({
        config: app_2.BaseAppConfig,
        ...appOptions,
        frameworkConfig: {
            ...appOptions.frameworkConfig,
            port: 0,
            compression: 0
        }
    });
    return new TestingFacade(app, options);
}
createTestingFacade.__type = ['appOptions', () => __ΩITestingFacadeOptions, 'options', 'createTestingFacade', 'P"2!n"2#8"/$'];
const __ΩMySQLConfig = [() => __ΩPick, () => app_2.BaseAppConfig, "MYSQL_HOST", "MYSQL_PORT", "MYSQL_USER", "MYSQL_PASSWORD_SECRET", 'MySQLConfig', 'P7"P.#.$.%.&Jo!#w\'y'];
const __ΩPGConfig = [() => __ΩPick, () => app_2.BaseAppConfig, "PG_HOST", "PG_PORT", "PG_USER", "PG_PASSWORD_SECRET", 'PGConfig', 'P7"P.#.$.%.&Jo!#w\'y'];
function setDefaultDatabaseConfig(config) {
    Object.assign(process.env, { ...config, ...process.env });
    if ('PG_HOST' in config) {
        process.env.PG_DATABASE ??= '_fake_database_';
    }
    else {
        process.env.MYSQL_DATABASE ??= '_fake_database_';
    }
}
setDefaultDatabaseConfig.__type = [() => __ΩMySQLConfig, () => __ΩPGConfig, 'config', 'setDefaultDatabaseConfig', 'PPn!n"J2#"/$'];
async function cleanupTestDatabases(prefix, except) {
    const dbNameRe = new RegExp(`^${prefix ?? 'test'}_(\\d+)_(\\d+)_(\\d+)$`);
    const adapter = getTestDbAdapter();
    function isProcessAlive(pid) {
        try {
            process.kill(pid, 0);
            return true;
        }
        catch {
            return false;
        }
    }
    isProcessAlive.__type = ['pid', 'isProcessAlive', 'P\'2!)/"'];
    function shouldDrop(db) {
        if (db === except)
            return false;
        const match = db.match(dbNameRe);
        if (!match)
            return false;
        const pid = parseInt(match[2]);
        return !isProcessAlive(pid);
    }
    shouldDrop.__type = ['db', 'shouldDrop', 'P&2!)/"'];
    if (adapter === 'postgres') {
        const pgAdapter = new postgres_1.PostgresDatabaseAdapter({
            host: process.env.PG_HOST,
            port: process.env.PG_PORT ? parseInt(process.env.PG_PORT) : 5432,
            user: process.env.PG_USER,
            password: process.env.PG_PASSWORD_SECRET,
            database: 'postgres'
        });
        const pgConn = await pgAdapter.connectionPool.getConnection();
        const result = await pgConn.execAndReturnAll(`SELECT datname AS "Database" FROM pg_database WHERE datistemplate = false`);
        const dbs = result.map(__assignType((r) => r.Database, ['Database', 'r', '', 'PP&4!M2""/#'])).filter(__assignType((db) => dbNameRe.test(db), ['db', '', 'P&2!"/"']));
        for (const db of dbs) {
            if (!shouldDrop(db))
                continue;
            await pgConn.run(`DROP DATABASE "${db}"`);
        }
        await pgConn.release();
        await pgAdapter.disconnect();
    }
    else {
        const dbAdapter = new mysql_1.MySQLDatabaseAdapter({
            host: process.env.MYSQL_HOST,
            port: process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT) : 3306,
            user: process.env.MYSQL_USER,
            password: process.env.MYSQL_PASSWORD_SECRET,
            database: 'mysql',
            allowPublicKeyRetrieval: true,
            trace: true
        });
        const dbConn = await dbAdapter.connectionPool.getConnection();
        const result = await dbConn.execAndReturnAll(`SHOW DATABASES`);
        const dbs = result.map(__assignType((r) => r.Database, ['Database', 'r', '', 'PP&4!M2""/#'])).filter(__assignType((db) => dbNameRe.test(db), ['db', '', 'P&2!"/"']));
        for (const db of dbs) {
            if (!shouldDrop(db))
                continue;
            await dbConn.run(`DROP DATABASE ${db}`);
        }
        await dbConn.release();
        await dbAdapter.disconnect();
    }
}
cleanupTestDatabases.__type = ['prefix', 'except', 'cleanupTestDatabases', 'P&2!8&2"8"/#'];
exports.TestingHelpers = {
    setDefaultDatabaseConfig,
    createTestingFacade,
    cleanupTestDatabases,
    defineEntityFixtures: fixtures_1.defineEntityFixtures,
    prepareEntityFixtures: fixtures_1.prepareEntityFixtures,
    loadEntityFixtures: fixtures_1.loadEntityFixtures,
    makeMockRequest: requests_1.makeMockRequest,
    installStandardHooks: requests_1.installStandardHooks,
    resetSrcModuleCache: requests_1.resetSrcModuleCache
};
//# sourceMappingURL=index.js.map