* fix: mark task as failed when PR creation fails Previously, when PR creation failed (e.g. invalid base branch), the task was still marked as 'completed' even though the PR was not created. This fix ensures: - postExecutionFlow returns prFailed/prError on failure - executeAndCompleteTask marks the task as failed when PR fails - selectAndExecuteTask runs postExecution before persisting result The pipeline path (executePipeline) already handled this correctly via EXIT_PR_CREATION_FAILED. * fix: use detectDefaultBranch instead of getCurrentBranch for PR base Previously, baseBranch for PR creation was set to HEAD's current branch via getCurrentBranch(). When the user was on a feature branch like 'codex/pr-16-review', PRs were created with --base codex/pr-16-review, which fails because it doesn't exist on the remote. Now uses detectDefaultBranch() (via git symbolic-ref refs/remotes/origin/HEAD) to always use the actual default branch (main/master) as the PR base. Affected paths: - resolveTask.ts (takt run) - selectAndExecute.ts (interactive mode) - pipeline/execute.ts (takt pipeline)
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { parse as parseYaml } from 'yaml';
|
|
import { createIsolatedEnv, updateIsolatedConfig, type IsolatedEnv } from '../helpers/isolated-env';
|
|
import { createTestRepo, type TestRepo } from '../helpers/test-repo';
|
|
import { runTakt } from '../helpers/takt-runner';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
function readFirstTask(repoPath: string): Record<string, unknown> {
|
|
const tasksPath = join(repoPath, '.takt', 'tasks.yaml');
|
|
const raw = readFileSync(tasksPath, 'utf-8');
|
|
const parsed = parseYaml(raw) as { tasks?: Array<Record<string, unknown>> } | null;
|
|
const first = parsed?.tasks?.[0];
|
|
if (!first) {
|
|
throw new Error(`No task record found in ${tasksPath}`);
|
|
}
|
|
return first;
|
|
}
|
|
|
|
// E2E更新時は docs/testing/e2e.md も更新すること
|
|
describe('E2E: Config priority (piece / autoPr)', () => {
|
|
let isolatedEnv: IsolatedEnv;
|
|
let testRepo: TestRepo;
|
|
|
|
beforeEach(() => {
|
|
isolatedEnv = createIsolatedEnv();
|
|
testRepo = createTestRepo();
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
testRepo.cleanup();
|
|
} catch {
|
|
// best-effort
|
|
}
|
|
try {
|
|
isolatedEnv.cleanup();
|
|
} catch {
|
|
// best-effort
|
|
}
|
|
});
|
|
|
|
it('should use configured piece in pipeline when --piece is omitted', () => {
|
|
const configuredPiecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
|
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
|
const projectConfigDir = join(testRepo.path, '.takt');
|
|
mkdirSync(projectConfigDir, { recursive: true });
|
|
writeFileSync(
|
|
join(projectConfigDir, 'config.yaml'),
|
|
`piece: ${JSON.stringify(configuredPiecePath)}\n`,
|
|
'utf-8',
|
|
);
|
|
|
|
const result = runTakt({
|
|
args: [
|
|
'--pipeline',
|
|
'--task', 'Pipeline run should resolve piece from config',
|
|
'--skip-git',
|
|
'--provider', 'mock',
|
|
],
|
|
cwd: testRepo.path,
|
|
env: {
|
|
...isolatedEnv.env,
|
|
TAKT_MOCK_SCENARIO: scenarioPath,
|
|
},
|
|
timeout: 240_000,
|
|
});
|
|
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout).toContain(`Running piece: ${configuredPiecePath}`);
|
|
expect(result.stdout).toContain(`Piece '${configuredPiecePath}' completed`);
|
|
}, 240_000);
|
|
|
|
it('should default auto_pr to true when unset in config/env', () => {
|
|
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
|
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
|
|
|
const result = runTakt({
|
|
args: [
|
|
'--task', 'Auto PR default behavior',
|
|
'--piece', piecePath,
|
|
'--create-worktree', 'yes',
|
|
'--provider', 'mock',
|
|
],
|
|
cwd: testRepo.path,
|
|
env: {
|
|
...isolatedEnv.env,
|
|
TAKT_MOCK_SCENARIO: scenarioPath,
|
|
},
|
|
timeout: 240_000,
|
|
});
|
|
|
|
// PR creation fails in test env (no gh remote), so exit code 1 is expected
|
|
// when auto_pr defaults to true. The task record is still persisted.
|
|
const task = readFirstTask(testRepo.path);
|
|
expect(task['auto_pr']).toBe(true);
|
|
}, 240_000);
|
|
|
|
it('should use auto_pr from config when set', () => {
|
|
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
|
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
|
updateIsolatedConfig(isolatedEnv.taktDir, { auto_pr: false });
|
|
|
|
const result = runTakt({
|
|
args: [
|
|
'--task', 'Auto PR from config',
|
|
'--piece', piecePath,
|
|
'--create-worktree', 'yes',
|
|
'--provider', 'mock',
|
|
],
|
|
cwd: testRepo.path,
|
|
env: {
|
|
...isolatedEnv.env,
|
|
TAKT_MOCK_SCENARIO: scenarioPath,
|
|
},
|
|
timeout: 240_000,
|
|
});
|
|
|
|
expect(result.exitCode).toBe(0);
|
|
const task = readFirstTask(testRepo.path);
|
|
expect(task['auto_pr']).toBe(false);
|
|
}, 240_000);
|
|
|
|
it('should prioritize env auto_pr over config', () => {
|
|
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
|
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
|
updateIsolatedConfig(isolatedEnv.taktDir, { auto_pr: false });
|
|
|
|
const result = runTakt({
|
|
args: [
|
|
'--task', 'Auto PR from env override',
|
|
'--piece', piecePath,
|
|
'--create-worktree', 'yes',
|
|
'--provider', 'mock',
|
|
],
|
|
cwd: testRepo.path,
|
|
env: {
|
|
...isolatedEnv.env,
|
|
TAKT_AUTO_PR: 'true',
|
|
TAKT_MOCK_SCENARIO: scenarioPath,
|
|
},
|
|
timeout: 240_000,
|
|
});
|
|
|
|
// PR creation fails in test env (no gh remote), so exit code 1 is expected
|
|
// when auto_pr is overridden to true. The task record is still persisted.
|
|
const task = readFirstTask(testRepo.path);
|
|
expect(task['auto_pr']).toBe(true);
|
|
}, 240_000);
|
|
});
|