forked from boos/BoosAPI
test:Add database migration service with normalization and masking functions
- Implemented `normalizeMigrationInput` to standardize database migration input for sqlite, mysql, and postgres. - Added `maskConnectionString` to obscure sensitive credentials in connection strings. - Created comprehensive tests for migration input normalization and connection string masking. - Established database schema creation logic for multiple dialects (sqlite, mysql, postgres). - Developed functions for managing database transactions, including begin, commit, and rollback. - Implemented data insertion logic from backup snapshots into the target database.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
type DbType = 'sqlite' | 'mysql' | 'postgres';
|
||||
|
||||
type CliOptions = {
|
||||
dbType?: string;
|
||||
dbUrl?: string;
|
||||
};
|
||||
|
||||
function parseArgs(argv: string[]): CliOptions {
|
||||
const options: CliOptions = {};
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i] || '';
|
||||
if (!arg.startsWith('--')) continue;
|
||||
|
||||
const [rawKey, rawValue] = arg.slice(2).split('=', 2);
|
||||
const key = rawKey.trim();
|
||||
const value = rawValue !== undefined ? rawValue : (argv[i + 1]?.startsWith('--') ? '' : argv[++i] || '');
|
||||
|
||||
if (key === 'db-type') options.dbType = value.trim();
|
||||
if (key === 'db-url') options.dbUrl = value.trim();
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function normalizeDbType(input: string | undefined): DbType {
|
||||
const normalized = (input || '').trim().toLowerCase();
|
||||
if (normalized === 'mysql') return 'mysql';
|
||||
if (normalized === 'postgres' || normalized === 'postgresql') return 'postgres';
|
||||
return 'sqlite';
|
||||
}
|
||||
|
||||
function normalizeFirstScalar(value: unknown): number | string | null {
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value === 'number' || typeof value === 'string') return value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return null;
|
||||
return normalizeFirstScalar(value[0]);
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
const record = value as Record<string, unknown>;
|
||||
if (Array.isArray(record.rows)) {
|
||||
return normalizeFirstScalar(record.rows);
|
||||
}
|
||||
|
||||
const scalar = Object.values(record)[0];
|
||||
return normalizeFirstScalar(scalar);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
if (options.dbType) process.env.DB_TYPE = options.dbType;
|
||||
if (options.dbUrl !== undefined) process.env.DB_URL = options.dbUrl;
|
||||
|
||||
const { config } = await import('../../src/server/config.js');
|
||||
const { db, runtimeDbDialect, closeDbConnections } = await import('../../src/server/db/index.js');
|
||||
|
||||
try {
|
||||
const dbType = normalizeDbType(config.dbType || options.dbType);
|
||||
const dbUrl = (config.dbUrl || '').trim();
|
||||
|
||||
if ((dbType === 'mysql' || dbType === 'postgres') && !dbUrl) {
|
||||
throw new Error(`DB_URL is required for DB_TYPE=${dbType}`);
|
||||
}
|
||||
|
||||
console.log(`[db-smoke] start dbType=${dbType} runtime=${runtimeDbDialect}`);
|
||||
if (dbUrl) {
|
||||
console.log(`[db-smoke] dbUrl=${dbUrl}`);
|
||||
} else {
|
||||
console.log('[db-smoke] dbUrl=(empty, using default sqlite path)');
|
||||
}
|
||||
|
||||
const pingRows = await db.execute(sql`select 1 as ok`);
|
||||
const pingScalar = normalizeFirstScalar(pingRows);
|
||||
if (Number(pingScalar) !== 1) {
|
||||
throw new Error(`unexpected ping result: ${JSON.stringify(pingRows)}`);
|
||||
}
|
||||
console.log('[db-smoke] ping ok');
|
||||
|
||||
const txRows = await db.transaction(async (tx: any) => tx.execute(sql`select 1 as ok`));
|
||||
const txScalar = normalizeFirstScalar(txRows);
|
||||
if (Number(txScalar) !== 1) {
|
||||
throw new Error(`unexpected transaction ping result: ${JSON.stringify(txRows)}`);
|
||||
}
|
||||
console.log('[db-smoke] transaction ok');
|
||||
|
||||
const versionRows = dbType === 'sqlite'
|
||||
? await db.execute(sql`select sqlite_version() as v`)
|
||||
: await db.execute(sql`select version() as v`);
|
||||
const version = normalizeFirstScalar(versionRows);
|
||||
if (typeof version !== 'string' || version.trim().length === 0) {
|
||||
throw new Error(`failed to read server version: ${JSON.stringify(versionRows)}`);
|
||||
}
|
||||
console.log(`[db-smoke] version=${version.slice(0, 120)}`);
|
||||
|
||||
console.log('[db-smoke] success');
|
||||
} finally {
|
||||
await closeDbConnections();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[db-smoke] failed');
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user