feat: 레거시 DB 순차 증분 이관 CLI 추가

This commit is contained in:
2026-08-18 12:35:24 +00:00
parent e910a635e6
commit da0e32f422
21 changed files with 1375 additions and 104 deletions
+56 -2
View File
@@ -1,8 +1,9 @@
import type { PoolClient } from 'pg';
import type { Pool as MariaPool } from 'mariadb';
import type { Pool as PgPool, PoolClient, QueryResult } from 'pg';
import { describe, expect, it, vi } from 'vitest';
import { upsertRows } from '../src/db.js';
import { mapMember, MEMBER_PRESERVED_COLUMNS, preflightMemberConflicts } from '../src/gateway.js';
import { mapMember, MEMBER_PRESERVED_COLUMNS, migrateGateway, preflightMemberConflicts } from '../src/gateway.js';
const memberRow = (overrides: Record<string, unknown> = {}) => ({
NO: 7,
@@ -110,4 +111,57 @@ describe('legacy gateway member migration', () => {
);
expect(String(query.mock.calls[0]?.[0])).toContain('FROM "app_user"');
});
it('reads only new immutable member logs during an incremental dry-run', async () => {
const fingerprint = 'a'.repeat(64);
const seen = new Set<string>();
const source = {
query: vi.fn(async (sql: string, values: readonly unknown[] = []) => {
if (sql.includes('MAX(`id`)') && sql.includes('member_log')) return [{ max_id: 2 }];
if (sql.includes('MAX(`date`)')) return [];
if (sql.includes('FROM `member`')) return [];
if (sql.includes('FROM `member_log`')) {
if (seen.has('member_log')) return [];
seen.add('member_log');
return Number(values[0]) < 2
? [
{
id: 2,
member_no: 7,
date: new Date('2026-08-18T00:00:00.000Z'),
action_type: 'login',
action: null,
},
]
: [];
}
return [];
}),
} as unknown as MariaPool;
const query = vi.fn(async (sql: string) => {
if (sql.includes('FROM "legacy_import_checkpoint"')) {
return {
rows: [{ source_fingerprint: fingerprint, last_legacy_id: '1' }],
rowCount: 1,
} as QueryResult;
}
return { rows: [], rowCount: 0 } as unknown as QueryResult;
});
const client = { query, release: vi.fn() } as unknown as PoolClient;
const target = { connect: vi.fn(async () => client) } as unknown as PgPool;
const summary = await migrateGateway(source, target, false, new Date('2026-08-18T00:00:00.000Z'), {
mode: 'incremental',
source: { key: 'fixture:gateway', fingerprint },
});
expect(summary.counts.member_log).toBe(1);
expect(summary.progress?.member_log).toMatchObject({
strategy: 'append',
startAfterId: '1',
endAtId: '2',
processed: 1,
});
expect(query.mock.calls.some(([sql]) => String(sql).includes('INSERT INTO "legacy_member_log"'))).toBe(false);
});
});