1
0
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:
cita
2026-03-05 17:38:52 +08:00
parent 0168592c95
commit 283c052fa5
79 changed files with 3410 additions and 1111 deletions
+111
View File
@@ -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);
});