kimi-code/packages/migration-legacy/test/migration-errors-log.test.ts
2026-05-22 15:54:50 +08:00

118 lines
4.9 KiB
TypeScript

import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeMigrationErrorsLog } from '../src/migration-errors-log.js';
import { migrationErrorsLogFile } from '../src/paths.js';
let tgt: string;
let src: string;
beforeEach(async () => {
tgt = await mkdtemp(join(tmpdir(), 'errlog-tgt-'));
src = await mkdtemp(join(tmpdir(), 'errlog-src-'));
});
afterEach(async () => {
await rm(tgt, { recursive: true, force: true });
await rm(src, { recursive: true, force: true });
});
describe('writeMigrationErrorsLog', () => {
it('appends a "no failures" marker block on a successful run', async () => {
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-19T00:00:00Z',
failures: [],
});
const log = await readFile(migrationErrorsLogFile(tgt), 'utf-8');
expect(log).toContain('===== migration run @ 2026-05-19T00:00:00Z =====');
expect(log).toContain('no failures.');
});
it('writes a diagnostic block per failure with a context.jsonl role histogram', async () => {
const sessionDir = join(src, 'ses-1');
await mkdir(sessionDir, { recursive: true });
await writeFile(
join(sessionDir, 'context.jsonl'),
'{"role":"_system_prompt","content":"x"}\n{"role":"user","content":"hi"}\n',
);
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-19T00:00:00Z',
failures: [{ sourcePath: sessionDir, reason: 'write failed: ENOSPC' }],
});
const log = await readFile(migrationErrorsLogFile(tgt), 'utf-8');
expect(log).toContain('===== migration run @ 2026-05-19T00:00:00Z =====');
expect(log).toContain('1 session(s) failed to migrate.');
expect(log).toContain(sessionDir);
expect(log).toContain('write failed: ENOSPC');
expect(log).toContain('context.jsonl: 2 lines');
expect(log).toContain('_system_prompt=1');
expect(log).toContain('user=1');
});
it('notes an unreadable context.jsonl rather than throwing', async () => {
const sessionDir = join(src, 'gone');
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-19T00:00:00Z',
failures: [{ sourcePath: sessionDir, reason: 'cannot read context.jsonl' }],
});
const log = await readFile(migrationErrorsLogFile(tgt), 'utf-8');
expect(log).toContain('context.jsonl: unreadable');
});
it('appends a second block when a later run also has failures', async () => {
// Cross-run history: a user who retries the migration and hits failures
// each time must end up with one log that shows every attempt — the team
// can analyze the full retry sequence from a single file.
const ses1 = join(src, 'ses-1');
const ses2 = join(src, 'ses-2');
await mkdir(ses1, { recursive: true });
await mkdir(ses2, { recursive: true });
await writeFile(join(ses1, 'context.jsonl'), '{"role":"user","content":"a"}\n');
await writeFile(join(ses2, 'context.jsonl'), '{"role":"user","content":"b"}\n');
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-19T00:00:00Z',
failures: [{ sourcePath: ses1, reason: 'first-run reason' }],
});
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-20T00:00:00Z',
failures: [{ sourcePath: ses2, reason: 'second-run reason' }],
});
const log = await readFile(migrationErrorsLogFile(tgt), 'utf-8');
// Both run headers survive (append-only, no overwrite).
const headerMatches = log.match(/===== migration run @ /g) ?? [];
expect(headerMatches).toHaveLength(2);
expect(log).toContain('2026-05-19T00:00:00Z');
expect(log).toContain('2026-05-20T00:00:00Z');
// Both runs' failure data survive.
expect(log).toContain('first-run reason');
expect(log).toContain('second-run reason');
expect(log).toContain(ses1);
expect(log).toContain(ses2);
});
it('appends after an earlier failed run when the later run has no failures', async () => {
// The earlier failure record must NOT be deleted by a successful retry —
// we want the timeline visible even after the user recovers.
const ses1 = join(src, 'ses-1');
await mkdir(ses1, { recursive: true });
await writeFile(join(ses1, 'context.jsonl'), '{"role":"user","content":"a"}\n');
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-19T00:00:00Z',
failures: [{ sourcePath: ses1, reason: 'first-run reason' }],
});
await writeMigrationErrorsLog(tgt, {
startedAt: '2026-05-20T00:00:00Z',
failures: [],
});
const log = await readFile(migrationErrorsLogFile(tgt), 'utf-8');
expect(log).toContain('===== migration run @ 2026-05-19T00:00:00Z =====');
expect(log).toContain('first-run reason');
expect(log).toContain('===== migration run @ 2026-05-20T00:00:00Z =====');
expect(log).toContain('no failures.');
});
});