mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-19 08:28:06 +02:00
144 lines
5.5 KiB
TypeScript
144 lines
5.5 KiB
TypeScript
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { GitService } from '../../core/index.js';
|
|
import { FileIngestTraceWriter } from '../ingest-trace.js';
|
|
import { runIsolatedWorkUnit } from './work-unit-executor.js';
|
|
|
|
async function makeGit() {
|
|
const homeDir = await mkdtemp(join(tmpdir(), 'ktx-isolated-wu-'));
|
|
const configDir = join(homeDir, 'config');
|
|
const git = new GitService({
|
|
storage: { configDir, homeDir },
|
|
git: {
|
|
userName: 'System User',
|
|
userEmail: 'system@example.com',
|
|
bootstrapMessage: 'init',
|
|
bootstrapAuthor: 'system',
|
|
bootstrapAuthorEmail: 'system@example.com',
|
|
},
|
|
});
|
|
await git.onModuleInit();
|
|
await mkdir(join(configDir, 'raw-sources/c1/fake/s'), { recursive: true });
|
|
await writeFile(join(configDir, 'raw-sources/c1/fake/s/a.json'), '{}\n');
|
|
await git.commitFiles(['raw-sources/c1/fake/s/a.json'], 'raw snapshot', 'System User', 'system@example.com');
|
|
return { homeDir, configDir, git, baseSha: await git.revParseHead() };
|
|
}
|
|
|
|
describe('runIsolatedWorkUnit', () => {
|
|
it('creates a child worktree at the ingestion base and persists a patch proposal', async () => {
|
|
const { homeDir, git, baseSha } = await makeGit();
|
|
const childDir = join(homeDir, '.worktrees/session-job-1-wu-1');
|
|
const sessionWorktreeService = {
|
|
create: vi.fn(async (_key: string, startSha: string) => {
|
|
await mkdir(join(homeDir, '.worktrees'), { recursive: true });
|
|
await git.addWorktree(childDir, 'session/job-1-wu-1', startSha);
|
|
const childGit = git.forWorktree(childDir);
|
|
return {
|
|
chatId: 'job-1-wu-1',
|
|
workdir: childDir,
|
|
branch: 'session/job-1-wu-1',
|
|
baseSha: startSha,
|
|
createdAt: new Date(),
|
|
git: childGit,
|
|
config: {},
|
|
};
|
|
}),
|
|
cleanup: vi.fn(async () => undefined),
|
|
};
|
|
const tracePath = join(homeDir, '.ktx/ingest-traces/job-1/trace.jsonl');
|
|
const trace = new FileIngestTraceWriter({
|
|
tracePath,
|
|
jobId: 'job-1',
|
|
connectionId: 'c1',
|
|
sourceKey: 'fake',
|
|
level: 'trace',
|
|
});
|
|
|
|
const result = await runIsolatedWorkUnit({
|
|
unitIndex: 0,
|
|
ingestionBaseSha: baseSha,
|
|
sessionWorktreeService: sessionWorktreeService as never,
|
|
patchDir: join(homeDir, '.ktx/ingest-patches/job-1'),
|
|
trace,
|
|
run: async (child) => {
|
|
await mkdir(join(child.workdir, 'wiki/global'), { recursive: true });
|
|
await writeFile(join(child.workdir, 'wiki/global/a.md'), '---\nsummary: A\nusage_mode: auto\n---\n\nBody\n');
|
|
await child.git.commitFiles(['wiki/global/a.md'], 'test: write wiki', 'KTX Test', 'system@ktx.local');
|
|
return {
|
|
unitKey: 'wu-1',
|
|
status: 'success',
|
|
preSha: baseSha,
|
|
postSha: await child.git.revParseHead(),
|
|
actions: [{ target: 'wiki', type: 'created', key: 'a', detail: 'A' }],
|
|
touchedSlSources: [],
|
|
};
|
|
},
|
|
workUnit: { unitKey: 'wu-1', rawFiles: ['a.json'], peerFileIndex: [], dependencyPaths: [] },
|
|
});
|
|
|
|
expect(sessionWorktreeService.create).toHaveBeenCalledWith('job-1-wu-1', baseSha);
|
|
expect(sessionWorktreeService.cleanup).toHaveBeenCalledWith(expect.any(Object), 'success');
|
|
expect(result.status).toBe('success');
|
|
if (result.status !== 'success') {
|
|
throw new Error('expected successful work unit');
|
|
}
|
|
const patchPath = result.patchPath;
|
|
if (!patchPath) {
|
|
throw new Error('expected patch path');
|
|
}
|
|
expect(patchPath).toContain('0000-wu-1.patch');
|
|
await expect(readFile(patchPath, 'utf-8')).resolves.toContain('wiki/global/a.md');
|
|
await expect(readFile(tracePath, 'utf-8')).resolves.toContain('work_unit_child_created');
|
|
});
|
|
|
|
it('removes child worktrees after failed WorkUnit outcomes are traced', async () => {
|
|
const { homeDir, git, baseSha } = await makeGit();
|
|
const childDir = join(homeDir, '.worktrees/session-job-1-wu-fail');
|
|
const sessionWorktreeService = {
|
|
create: vi.fn(async (_key: string, startSha: string) => {
|
|
await mkdir(join(homeDir, '.worktrees'), { recursive: true });
|
|
await git.addWorktree(childDir, 'session/job-1-wu-fail', startSha);
|
|
return {
|
|
chatId: 'job-1-wu-fail',
|
|
workdir: childDir,
|
|
branch: 'session/job-1-wu-fail',
|
|
baseSha: startSha,
|
|
createdAt: new Date(),
|
|
git: git.forWorktree(childDir),
|
|
config: {},
|
|
};
|
|
}),
|
|
cleanup: vi.fn(async () => undefined),
|
|
};
|
|
const trace = new FileIngestTraceWriter({
|
|
tracePath: join(homeDir, '.ktx/ingest-traces/job-1/trace.jsonl'),
|
|
jobId: 'job-1',
|
|
connectionId: 'c1',
|
|
sourceKey: 'fake',
|
|
level: 'trace',
|
|
});
|
|
|
|
const result = await runIsolatedWorkUnit({
|
|
unitIndex: 0,
|
|
ingestionBaseSha: baseSha,
|
|
sessionWorktreeService: sessionWorktreeService as never,
|
|
patchDir: join(homeDir, '.ktx/ingest-patches/job-1'),
|
|
trace,
|
|
run: async () => ({
|
|
unitKey: 'wu-fail',
|
|
status: 'failed',
|
|
reason: 'agent loop errored',
|
|
preSha: baseSha,
|
|
postSha: baseSha,
|
|
actions: [],
|
|
touchedSlSources: [],
|
|
}),
|
|
workUnit: { unitKey: 'wu-fail', rawFiles: ['a.json'], peerFileIndex: [], dependencyPaths: [] },
|
|
});
|
|
|
|
expect(result.status).toBe('failed');
|
|
expect(sessionWorktreeService.cleanup).toHaveBeenCalledWith(expect.any(Object), 'success');
|
|
});
|
|
});
|