UnitTestとE2ETestをリファクタリング
This commit is contained in:
parent
5478d766cd
commit
41bde30adc
26
e2e/helpers/session-log.ts
Normal file
26
e2e/helpers/session-log.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/**
|
||||
* Read session NDJSON log records from a piece execution run.
|
||||
* Finds the first .jsonl file whose first record is `piece_start`.
|
||||
*/
|
||||
export function readSessionRecords(repoPath: string): Array<Record<string, unknown>> {
|
||||
const runsDir = join(repoPath, '.takt', 'runs');
|
||||
const runDirs = readdirSync(runsDir).sort();
|
||||
|
||||
for (const runDir of runDirs) {
|
||||
const logsDir = join(runsDir, runDir, 'logs');
|
||||
const logFiles = readdirSync(logsDir).filter((file) => file.endsWith('.jsonl'));
|
||||
for (const file of logFiles) {
|
||||
const content = readFileSync(join(logsDir, file), 'utf-8').trim();
|
||||
if (!content) continue;
|
||||
const records = content.split('\n').map((line) => JSON.parse(line) as Record<string, unknown>);
|
||||
if (records[0]?.type === 'piece_start') {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Session NDJSON log not found');
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
import { rmSync } from 'node:fs';
|
||||
import { mkdtempSync } from 'node:fs';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
export interface LocalRepo {
|
||||
path: string;
|
||||
cleanup: () => void;
|
||||
}
|
||||
|
||||
export interface TestRepo {
|
||||
path: string;
|
||||
repoName: string;
|
||||
@ -11,6 +15,26 @@ export interface TestRepo {
|
||||
cleanup: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a local git repository in a temporary directory.
|
||||
* Use this for tests that don't need a remote (GitHub) repository.
|
||||
*/
|
||||
export function createLocalRepo(): LocalRepo {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
rmSync(repoPath, { recursive: true, force: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateTestRepoOptions {
|
||||
/** Skip creating a test branch (stay on default branch). Use for pipeline tests. */
|
||||
skipBranch?: boolean;
|
||||
|
||||
@ -1,31 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-catalog-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Catalog command (takt catalog)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,31 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-clear-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Clear sessions command (takt clear)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,31 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-config-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Config command (takt config)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,31 +1,15 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, existsSync, readdirSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync, existsSync, readdirSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-export-cc-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Export-cc command (takt export-cc)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
let fakeHome: string;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,31 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-help-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Help command (takt --help)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,36 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-prompt-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Prompt preview command (takt prompt)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,31 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-reset-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Reset categories command (takt reset categories)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,31 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-switch-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Switch piece command (takt switch)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,59 +1,22 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
createIsolatedEnv,
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
import { readSessionRecords } from '../helpers/session-log';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-cycle-detect-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readSessionRecords(repoPath: string): Array<Record<string, unknown>> {
|
||||
const runsDir = join(repoPath, '.takt', 'runs');
|
||||
const runDirs = readdirSync(runsDir).sort();
|
||||
|
||||
for (const runDir of runDirs) {
|
||||
const logsDir = join(runsDir, runDir, 'logs');
|
||||
const logFiles = readdirSync(logsDir).filter((file) => file.endsWith('.jsonl'));
|
||||
for (const file of logFiles) {
|
||||
const content = readFileSync(join(logsDir, file), 'utf-8').trim();
|
||||
if (!content) continue;
|
||||
const records = content.split('\n').map((line) => JSON.parse(line) as Record<string, unknown>);
|
||||
if (records[0]?.type === 'piece_start') {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Session NDJSON log not found');
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Cycle detection via loop_monitors (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,40 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { existsSync, readFileSync, mkdirSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
/**
|
||||
* Create a minimal local git repository for eject tests.
|
||||
* No GitHub access needed — just a local git init.
|
||||
*/
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-eject-e2e-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
// Create initial commit so branch exists
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try {
|
||||
rmSync(repoPath, { recursive: true, force: true });
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Eject builtin pieces (takt eject)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,36 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-error-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Error handling edge cases (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,35 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-model-override-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: --model option override (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,55 +1,18 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
import { readSessionRecords } from '../helpers/session-log';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-sequential-step-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readSessionRecords(repoPath: string): Array<Record<string, unknown>> {
|
||||
const runsDir = join(repoPath, '.takt', 'runs');
|
||||
const runDirs = readdirSync(runsDir).sort();
|
||||
|
||||
for (const runDir of runDirs) {
|
||||
const logsDir = join(runsDir, runDir, 'logs');
|
||||
const logFiles = readdirSync(logsDir).filter((file) => file.endsWith('.jsonl'));
|
||||
for (const file of logFiles) {
|
||||
const content = readFileSync(join(logsDir, file), 'utf-8').trim();
|
||||
if (!content) continue;
|
||||
const records = content.split('\n').map((line) => JSON.parse(line) as Record<string, unknown>);
|
||||
if (records[0]?.type === 'piece_start') {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Session NDJSON log not found');
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Sequential multi-step session log transitions (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,36 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-piece-err-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Piece error handling (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -3,29 +3,13 @@ import { resolve, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-pipeline-local-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createNonGitDir(): { path: string; cleanup: () => void } {
|
||||
const dirPath = mkdtempSync(join(tmpdir(), 'takt-e2e-pipeline-nongit-'));
|
||||
writeFileSync(join(dirPath, 'README.md'), '# non-git\n');
|
||||
@ -40,7 +24,7 @@ function createNonGitDir(): { path: string; cleanup: () => void } {
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Pipeline --skip-git on local/non-git directories (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,40 +1,21 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
createIsolatedEnv,
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-provider-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Provider error handling (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,36 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-quiet-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Quiet mode (--quiet)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,39 +1,22 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import {
|
||||
createIsolatedEnv,
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-report-file-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Report file output (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,40 +1,23 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
createIsolatedEnv,
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-run-multi-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Run multiple tasks (takt run)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
|
||||
|
||||
@ -1,55 +1,18 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, writeFileSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
import { readSessionRecords } from '../helpers/session-log';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-session-log-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readSessionRecords(repoPath: string): Array<Record<string, unknown>> {
|
||||
const runsDir = join(repoPath, '.takt', 'runs');
|
||||
const runDirs = readdirSync(runsDir).sort();
|
||||
|
||||
for (const runDir of runDirs) {
|
||||
const logsDir = join(runsDir, runDir, 'logs');
|
||||
const logFiles = readdirSync(logsDir).filter((file) => file.endsWith('.jsonl'));
|
||||
for (const file of logFiles) {
|
||||
const content = readFileSync(join(logsDir, file), 'utf-8').trim();
|
||||
if (!content) continue;
|
||||
const records = content.split('\n').map((line) => JSON.parse(line) as Record<string, unknown>);
|
||||
if (records[0]?.type === 'piece_start') {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Session NDJSON log not found');
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Session NDJSON log output (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -1,40 +1,23 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
createIsolatedEnv,
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-contentfile-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Task content_file reference (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
|
||||
|
||||
@ -1,32 +1,15 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { resolve, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { createIsolatedEnv, updateIsolatedConfig, type IsolatedEnv } from '../helpers/isolated-env';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
import { createLocalRepo, type LocalRepo } from '../helpers/test-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createLocalRepo(): { path: string; cleanup: () => void } {
|
||||
const repoPath = mkdtempSync(join(tmpdir(), 'takt-e2e-task-status-'));
|
||||
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
return {
|
||||
path: repoPath,
|
||||
cleanup: () => {
|
||||
try { rmSync(repoPath, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeSinglePendingTask(repoPath: string, piecePath: string): void {
|
||||
const now = new Date().toISOString();
|
||||
mkdirSync(join(repoPath, '.takt'), { recursive: true });
|
||||
@ -49,7 +32,7 @@ function writeSinglePendingTask(repoPath: string, piecePath: string): void {
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Task status persistence in tasks.yaml (mock)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let repo: { path: string; cleanup: () => void };
|
||||
let repo: LocalRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
isolatedEnv = createIsolatedEnv();
|
||||
|
||||
@ -6,17 +6,9 @@
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { handleBlocked } from '../core/piece/engine/blocked-handler.js';
|
||||
import type { PieceMovement, AgentResponse } from '../core/models/types.js';
|
||||
import type { AgentResponse } from '../core/models/types.js';
|
||||
import type { PieceEngineOptions } from '../core/piece/types.js';
|
||||
|
||||
function makeMovement(): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
};
|
||||
}
|
||||
import { makeMovement } from './test-helpers.js';
|
||||
|
||||
function makeResponse(content: string): AgentResponse {
|
||||
return {
|
||||
|
||||
@ -10,7 +10,8 @@ import { mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { PieceConfig, PieceMovement, AgentResponse, PieceRule } from '../core/models/index.js';
|
||||
import type { PieceConfig, PieceMovement, AgentResponse } from '../core/models/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
|
||||
// --- Mock imports (consumers must call vi.mock before importing this) ---
|
||||
|
||||
@ -22,6 +23,8 @@ import { generateReportDir } from '../shared/utils/index.js';
|
||||
|
||||
// --- Factory functions ---
|
||||
|
||||
export { makeRule };
|
||||
|
||||
export function makeResponse(overrides: Partial<AgentResponse> = {}): AgentResponse {
|
||||
return {
|
||||
persona: 'test-agent',
|
||||
@ -33,10 +36,6 @@ export function makeResponse(overrides: Partial<AgentResponse> = {}): AgentRespo
|
||||
};
|
||||
}
|
||||
|
||||
export function makeRule(condition: string, next: string, extra: Partial<PieceRule> = {}): PieceRule {
|
||||
return { condition, next, ...extra };
|
||||
}
|
||||
|
||||
export function makeMovement(name: string, overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name,
|
||||
|
||||
@ -9,31 +9,7 @@ import {
|
||||
escapeTemplateChars,
|
||||
replaceTemplatePlaceholders,
|
||||
} from '../core/piece/instruction/escape.js';
|
||||
import type { PieceMovement } from '../core/models/types.js';
|
||||
import type { InstructionContext } from '../core/piece/instruction/instruction-context.js';
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeContext(overrides: Partial<InstructionContext> = {}): InstructionContext {
|
||||
return {
|
||||
task: 'test task',
|
||||
iteration: 1,
|
||||
maxMovements: 10,
|
||||
movementIteration: 1,
|
||||
cwd: '/tmp/test',
|
||||
projectCwd: '/tmp/project',
|
||||
userInputs: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
import { makeMovement, makeInstructionContext } from './test-helpers.js';
|
||||
|
||||
describe('escapeTemplateChars', () => {
|
||||
it('should replace curly braces with full-width equivalents', () => {
|
||||
@ -62,7 +38,7 @@ describe('escapeTemplateChars', () => {
|
||||
describe('replaceTemplatePlaceholders', () => {
|
||||
it('should replace {task} placeholder', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ task: 'implement feature X' });
|
||||
const ctx = makeInstructionContext({ task: 'implement feature X' });
|
||||
const template = 'Your task is: {task}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -71,7 +47,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should escape braces in task content', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ task: 'fix {bug} in code' });
|
||||
const ctx = makeInstructionContext({ task: 'fix {bug} in code' });
|
||||
const template = '{task}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -80,7 +56,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {iteration} and {max_movements}', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ iteration: 3, maxMovements: 20 });
|
||||
const ctx = makeInstructionContext({ iteration: 3, maxMovements: 20 });
|
||||
const template = 'Iteration {iteration}/{max_movements}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -89,7 +65,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {movement_iteration}', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ movementIteration: 5 });
|
||||
const ctx = makeInstructionContext({ movementIteration: 5 });
|
||||
const template = 'Movement run #{movement_iteration}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -98,7 +74,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {previous_response} when passPreviousResponse is true', () => {
|
||||
const step = makeMovement({ passPreviousResponse: true });
|
||||
const ctx = makeContext({
|
||||
const ctx = makeInstructionContext({
|
||||
previousOutput: {
|
||||
persona: 'coder',
|
||||
status: 'done',
|
||||
@ -114,7 +90,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should prefer preprocessed previous response text when provided', () => {
|
||||
const step = makeMovement({ passPreviousResponse: true });
|
||||
const ctx = makeContext({
|
||||
const ctx = makeInstructionContext({
|
||||
previousOutput: {
|
||||
persona: 'coder',
|
||||
status: 'done',
|
||||
@ -131,7 +107,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {previous_response} with empty string when no previous output', () => {
|
||||
const step = makeMovement({ passPreviousResponse: true });
|
||||
const ctx = makeContext();
|
||||
const ctx = makeInstructionContext();
|
||||
const template = 'Previous: {previous_response}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -140,7 +116,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should not replace {previous_response} when passPreviousResponse is false', () => {
|
||||
const step = makeMovement({ passPreviousResponse: false });
|
||||
const ctx = makeContext({
|
||||
const ctx = makeInstructionContext({
|
||||
previousOutput: {
|
||||
persona: 'coder',
|
||||
status: 'done',
|
||||
@ -156,7 +132,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {user_inputs} with joined inputs', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ userInputs: ['input 1', 'input 2', 'input 3'] });
|
||||
const ctx = makeInstructionContext({ userInputs: ['input 1', 'input 2', 'input 3'] });
|
||||
const template = 'Inputs: {user_inputs}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -165,7 +141,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {report_dir} with report directory', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports/run-1' });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports/run-1' });
|
||||
const template = 'Reports: {report_dir}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -174,7 +150,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should replace {report:filename} with full path', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports' });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports' });
|
||||
const template = 'Read {report:review.md} and {report:plan.md}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
@ -183,7 +159,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should handle template with multiple different placeholders', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({
|
||||
const ctx = makeInstructionContext({
|
||||
task: 'test task',
|
||||
iteration: 2,
|
||||
maxMovements: 5,
|
||||
@ -198,7 +174,7 @@ describe('replaceTemplatePlaceholders', () => {
|
||||
|
||||
it('should leave unreplaced placeholders when reportDir is undefined', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ reportDir: undefined });
|
||||
const ctx = makeInstructionContext({ reportDir: undefined });
|
||||
const template = 'Dir: {report_dir} File: {report:test.md}';
|
||||
|
||||
const result = replaceTemplatePlaceholders(template, step, ctx);
|
||||
|
||||
@ -10,31 +10,8 @@ import {
|
||||
renderReportContext,
|
||||
renderReportOutputInstruction,
|
||||
} from '../core/piece/instruction/InstructionBuilder.js';
|
||||
import type { PieceMovement, OutputContractEntry } from '../core/models/types.js';
|
||||
import type { InstructionContext } from '../core/piece/instruction/instruction-context.js';
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeContext(overrides: Partial<InstructionContext> = {}): InstructionContext {
|
||||
return {
|
||||
task: 'test task',
|
||||
iteration: 1,
|
||||
maxMovements: 10,
|
||||
movementIteration: 1,
|
||||
cwd: '/tmp/test',
|
||||
projectCwd: '/tmp/project',
|
||||
userInputs: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
import type { OutputContractEntry } from '../core/models/types.js';
|
||||
import { makeMovement, makeInstructionContext } from './test-helpers.js';
|
||||
|
||||
describe('isOutputContractItem', () => {
|
||||
it('should return true for OutputContractItem (has name)', () => {
|
||||
@ -84,19 +61,19 @@ describe('renderReportContext', () => {
|
||||
describe('renderReportOutputInstruction', () => {
|
||||
it('should return empty string when no output contracts', () => {
|
||||
const step = makeMovement();
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports' });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports' });
|
||||
expect(renderReportOutputInstruction(step, ctx, 'en')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string when no reportDir', () => {
|
||||
const step = makeMovement({ outputContracts: [{ name: 'report.md' }] });
|
||||
const ctx = makeContext();
|
||||
const ctx = makeInstructionContext();
|
||||
expect(renderReportOutputInstruction(step, ctx, 'en')).toBe('');
|
||||
});
|
||||
|
||||
it('should render English single-file instruction', () => {
|
||||
const step = makeMovement({ outputContracts: [{ name: 'report.md' }] });
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports', movementIteration: 2 });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports', movementIteration: 2 });
|
||||
|
||||
const result = renderReportOutputInstruction(step, ctx, 'en');
|
||||
expect(result).toContain('Report output');
|
||||
@ -108,7 +85,7 @@ describe('renderReportOutputInstruction', () => {
|
||||
const step = makeMovement({
|
||||
outputContracts: [{ name: 'plan.md' }, { name: 'review.md' }],
|
||||
});
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports' });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports' });
|
||||
|
||||
const result = renderReportOutputInstruction(step, ctx, 'en');
|
||||
expect(result).toContain('Report Files');
|
||||
@ -116,7 +93,7 @@ describe('renderReportOutputInstruction', () => {
|
||||
|
||||
it('should render Japanese single-file instruction', () => {
|
||||
const step = makeMovement({ outputContracts: [{ name: 'report.md' }] });
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports', movementIteration: 1 });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports', movementIteration: 1 });
|
||||
|
||||
const result = renderReportOutputInstruction(step, ctx, 'ja');
|
||||
expect(result).toContain('レポート出力');
|
||||
@ -128,7 +105,7 @@ describe('renderReportOutputInstruction', () => {
|
||||
const step = makeMovement({
|
||||
outputContracts: [{ name: 'plan.md' }, { name: 'review.md' }],
|
||||
});
|
||||
const ctx = makeContext({ reportDir: '/tmp/reports' });
|
||||
const ctx = makeInstructionContext({ reportDir: '/tmp/reports' });
|
||||
|
||||
const result = renderReportOutputInstruction(step, ctx, 'ja');
|
||||
expect(result).toContain('Report Files');
|
||||
|
||||
@ -15,6 +15,7 @@ import { tmpdir } from 'node:os';
|
||||
import { setMockScenario, resetScenario } from '../infra/mock/index.js';
|
||||
import type { PieceConfig, PieceMovement, PieceRule } from '../core/models/index.js';
|
||||
import { detectRuleIndex } from '../infra/claude/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
import { callAiJudge } from '../agents/ai-judge.js';
|
||||
|
||||
// --- Mocks ---
|
||||
@ -56,10 +57,6 @@ import { PieceEngine } from '../core/piece/index.js';
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
function makeRule(condition: string, next: string): PieceRule {
|
||||
return { condition, next };
|
||||
}
|
||||
|
||||
function makeMovement(name: string, agentPath: string, rules: PieceRule[]): PieceMovement {
|
||||
return {
|
||||
name,
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import type { PieceMovement, PieceRule, AgentResponse } from '../core/models/index.js';
|
||||
import type { PieceMovement, AgentResponse } from '../core/models/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
|
||||
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||
@ -34,10 +35,6 @@ function buildStatusJudgmentInstruction(movement: PieceMovement, ctx: StatusJudg
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
function makeRule(condition: string, next: string, extra?: Partial<PieceRule>): PieceRule {
|
||||
return { condition, next, ...extra };
|
||||
}
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-step',
|
||||
|
||||
@ -16,6 +16,7 @@ import { tmpdir } from 'node:os';
|
||||
import { setMockScenario, resetScenario } from '../infra/mock/index.js';
|
||||
import type { PieceConfig, PieceMovement, PieceRule } from '../core/models/index.js';
|
||||
import { detectRuleIndex } from '../infra/claude/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
import { callAiJudge } from '../agents/ai-judge.js';
|
||||
|
||||
// --- Mocks (minimal — only infrastructure, not core logic) ---
|
||||
@ -59,10 +60,6 @@ import { PieceEngine } from '../core/piece/index.js';
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
function makeRule(condition: string, next: string): PieceRule {
|
||||
return { condition, next };
|
||||
}
|
||||
|
||||
function makeMovement(name: string, agentPath: string, rules: PieceRule[]): PieceMovement {
|
||||
return {
|
||||
name,
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import type { PieceMovement, PieceState, PieceRule, AgentResponse } from '../core/models/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
|
||||
// --- Mocks ---
|
||||
|
||||
@ -39,10 +40,6 @@ import type { RuleMatch, RuleEvaluatorContext } from '../core/piece/index.js';
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
function makeRule(condition: string, next: string, extra?: Partial<PieceRule>): PieceRule {
|
||||
return { condition, next, ...extra };
|
||||
}
|
||||
|
||||
function makeMovement(
|
||||
name: string,
|
||||
rules: PieceRule[],
|
||||
|
||||
@ -16,6 +16,7 @@ import { tmpdir } from 'node:os';
|
||||
import { setMockScenario, resetScenario } from '../infra/mock/index.js';
|
||||
import type { PieceConfig, PieceMovement, PieceRule } from '../core/models/index.js';
|
||||
import { detectRuleIndex } from '../infra/claude/index.js';
|
||||
import { makeRule } from './test-helpers.js';
|
||||
import { callAiJudge } from '../agents/ai-judge.js';
|
||||
|
||||
// --- Mocks ---
|
||||
@ -61,10 +62,6 @@ import { PieceEngine } from '../core/piece/index.js';
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
function makeRule(condition: string, next: string): PieceRule {
|
||||
return { condition, next };
|
||||
}
|
||||
|
||||
function createTestEnv(): { dir: string; agentPath: string } {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'takt-it-3ph-'));
|
||||
mkdirSync(join(dir, '.takt', 'reports', 'test-report-dir'), { recursive: true });
|
||||
|
||||
@ -15,17 +15,7 @@ import {
|
||||
JudgmentStrategyFactory,
|
||||
type JudgmentContext,
|
||||
} from '../core/piece/judgment/FallbackStrategy.js';
|
||||
import type { PieceMovement } from '../core/models/types.js';
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
import { makeMovement } from './test-helpers.js';
|
||||
|
||||
function makeContext(overrides: Partial<JudgmentContext> = {}): JudgmentContext {
|
||||
return {
|
||||
|
||||
@ -6,17 +6,8 @@
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { RuleEvaluator, type RuleEvaluatorContext } from '../core/piece/evaluation/RuleEvaluator.js';
|
||||
import type { PieceMovement, PieceState } from '../core/models/types.js';
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
import type { PieceState } from '../core/models/types.js';
|
||||
import { makeMovement } from './test-helpers.js';
|
||||
|
||||
function makeState(): PieceState {
|
||||
return {
|
||||
|
||||
@ -12,17 +12,8 @@ import {
|
||||
getAutoSelectedTag,
|
||||
getReportFiles,
|
||||
} from '../core/piece/evaluation/rule-utils.js';
|
||||
import type { PieceMovement, OutputContractEntry } from '../core/models/types.js';
|
||||
|
||||
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
import type { OutputContractEntry } from '../core/models/types.js';
|
||||
import { makeMovement } from './test-helpers.js';
|
||||
|
||||
describe('hasTagBasedRules', () => {
|
||||
it('should return false when movement has no rules', () => {
|
||||
|
||||
36
src/__tests__/test-helpers.ts
Normal file
36
src/__tests__/test-helpers.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Shared helpers for unit tests and integration tests.
|
||||
*
|
||||
* Unlike engine-test-helpers.ts, this file has no mock dependencies and
|
||||
* can be safely imported from any test file without requiring vi.mock() setup.
|
||||
*/
|
||||
|
||||
import type { PieceMovement, PieceRule } from '../core/models/types.js';
|
||||
import type { InstructionContext } from '../core/piece/instruction/instruction-context.js';
|
||||
|
||||
export function makeRule(condition: string, next: string, extra: Partial<PieceRule> = {}): PieceRule {
|
||||
return { condition, next, ...extra };
|
||||
}
|
||||
|
||||
export function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
|
||||
return {
|
||||
name: 'test-movement',
|
||||
personaDisplayName: 'tester',
|
||||
instructionTemplate: '',
|
||||
passPreviousResponse: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function makeInstructionContext(overrides: Partial<InstructionContext> = {}): InstructionContext {
|
||||
return {
|
||||
task: 'test task',
|
||||
iteration: 1,
|
||||
maxMovements: 10,
|
||||
movementIteration: 1,
|
||||
cwd: '/tmp/test',
|
||||
projectCwd: '/tmp/project',
|
||||
userInputs: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user