test(09-01): create conflict hand-back round-trip E2E tests
- Full conflict cycle: detect conflict -> agent resolves -> merge succeeds - Conflict resolution preserves original task context - Multiple sequential conflicts resolved in order - 3 conflict round-trip tests passing
This commit is contained in:
551
src/test/e2e/extended-scenarios.test.ts
Normal file
551
src/test/e2e/extended-scenarios.test.ts
Normal file
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* E2E Tests for Extended Scenarios
|
||||
*
|
||||
* Tests extended scenarios in dispatch/coordination flow:
|
||||
* - Conflict hand-back round-trip (conflict -> agent resolves -> merge succeeds)
|
||||
* - Multi-agent parallel work and completion
|
||||
*
|
||||
* Uses TestHarness from src/test/ for full system wiring.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import {
|
||||
createTestHarness,
|
||||
SIMPLE_FIXTURE,
|
||||
PARALLEL_FIXTURE,
|
||||
COMPLEX_FIXTURE,
|
||||
type TestHarness,
|
||||
} from '../index.js';
|
||||
import type {
|
||||
MergeConflictedEvent,
|
||||
MergeCompletedEvent,
|
||||
TaskQueuedEvent,
|
||||
AgentStoppedEvent,
|
||||
AgentCrashedEvent,
|
||||
} from '../../events/types.js';
|
||||
|
||||
describe('E2E Extended Scenarios', () => {
|
||||
let harness: TestHarness;
|
||||
|
||||
beforeEach(() => {
|
||||
harness = createTestHarness();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
harness.cleanup();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Conflict Hand-back Round-trip
|
||||
// ===========================================================================
|
||||
|
||||
describe('Conflict hand-back round-trip', () => {
|
||||
it('conflict triggers resolution task, agent resolves, merge succeeds', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
||||
const taskAId = seeded.tasks.get('Task A')!;
|
||||
|
||||
// Step 1: Complete Task A
|
||||
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
||||
|
||||
// Step 2: Create agent in agentRepository with worktreeId
|
||||
const worktreeId = `wt-${taskAId.slice(0, 6)}`;
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${taskAId.slice(0, 6)}`,
|
||||
worktreeId,
|
||||
taskId: taskAId,
|
||||
status: 'idle',
|
||||
});
|
||||
|
||||
// Step 3: Create worktree via MockWorktreeManager
|
||||
await harness.worktreeManager.create(worktreeId, 'feature-task-a');
|
||||
|
||||
// Step 4: Set merge conflict result for first merge attempt
|
||||
harness.worktreeManager.setMergeResult(worktreeId, {
|
||||
success: false,
|
||||
conflicts: ['src/shared.ts', 'src/types.ts'],
|
||||
message: 'Merge conflict in 2 files',
|
||||
});
|
||||
|
||||
// Step 5: Queue and process merge (should fail with conflict)
|
||||
await harness.coordinationManager.queueMerge(taskAId);
|
||||
harness.clearEvents();
|
||||
const conflictResults = await harness.coordinationManager.processMerges('main');
|
||||
|
||||
// Verify: merge failed with conflict
|
||||
expect(conflictResults.length).toBe(1);
|
||||
expect(conflictResults[0].success).toBe(false);
|
||||
expect(conflictResults[0].conflicts).toEqual(['src/shared.ts', 'src/types.ts']);
|
||||
|
||||
// Verify: merge:conflicted event emitted
|
||||
const conflictedEvents = harness.getEventsByType('merge:conflicted');
|
||||
expect(conflictedEvents.length).toBe(1);
|
||||
const conflictPayload = (conflictedEvents[0] as MergeConflictedEvent).payload;
|
||||
expect(conflictPayload.taskId).toBe(taskAId);
|
||||
expect(conflictPayload.conflictingFiles).toEqual(['src/shared.ts', 'src/types.ts']);
|
||||
|
||||
// Verify: original task marked blocked
|
||||
const originalTask = await harness.taskRepository.findById(taskAId);
|
||||
expect(originalTask?.status).toBe('blocked');
|
||||
|
||||
// Note: CoordinationManager.handleConflict updates task status to blocked
|
||||
// but does not emit task:blocked event (that's emitted by DispatchManager.blockTask)
|
||||
|
||||
// Verify: task:queued event emitted for resolution task
|
||||
const queuedEvents = harness.getEventsByType('task:queued');
|
||||
const resolutionTaskEvent = queuedEvents.find(
|
||||
(e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
|
||||
);
|
||||
expect(resolutionTaskEvent).toBeDefined();
|
||||
|
||||
// Step 6: Clear the merge conflict (setMergeResult to success)
|
||||
harness.worktreeManager.setMergeResult(worktreeId, {
|
||||
success: true,
|
||||
message: 'Merged successfully',
|
||||
});
|
||||
|
||||
// Step 7: Re-queue original task for merge (simulating resolution completed)
|
||||
// In a real system, the resolution task would fix conflicts and re-queue
|
||||
// Here we simulate by clearing conflict and re-queuing
|
||||
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
||||
harness.clearEvents();
|
||||
|
||||
await harness.coordinationManager.queueMerge(taskAId);
|
||||
const successResults = await harness.coordinationManager.processMerges('main');
|
||||
|
||||
// Verify: merge succeeded
|
||||
expect(successResults.length).toBe(1);
|
||||
expect(successResults[0].taskId).toBe(taskAId);
|
||||
expect(successResults[0].success).toBe(true);
|
||||
|
||||
// Verify: merge:completed event for original task
|
||||
const completedEvents = harness.getEventsByType('merge:completed');
|
||||
expect(completedEvents.length).toBe(1);
|
||||
const completedPayload = (completedEvents[0] as MergeCompletedEvent).payload;
|
||||
expect(completedPayload.taskId).toBe(taskAId);
|
||||
});
|
||||
|
||||
it('conflict resolution preserves original task context', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
||||
const taskAId = seeded.tasks.get('Task A')!;
|
||||
|
||||
// Complete Task A
|
||||
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
||||
|
||||
// Create agent and worktree
|
||||
const worktreeId = `wt-${taskAId.slice(0, 6)}`;
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${taskAId.slice(0, 6)}`,
|
||||
worktreeId,
|
||||
taskId: taskAId,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.worktreeManager.create(worktreeId, 'feature-task-a');
|
||||
|
||||
// Set conflict
|
||||
harness.worktreeManager.setMergeResult(worktreeId, {
|
||||
success: false,
|
||||
conflicts: ['src/conflict-file.ts'],
|
||||
message: 'Merge conflict',
|
||||
});
|
||||
|
||||
// Process merge to trigger conflict handling
|
||||
await harness.coordinationManager.queueMerge(taskAId);
|
||||
harness.clearEvents();
|
||||
await harness.coordinationManager.processMerges('main');
|
||||
|
||||
// Get the resolution task from task:queued events
|
||||
const queuedEvents = harness.getEventsByType('task:queued');
|
||||
expect(queuedEvents.length).toBeGreaterThan(0);
|
||||
|
||||
// Find resolution task (the one that isn't the original task)
|
||||
const resolutionTaskQueuedEvent = queuedEvents.find(
|
||||
(e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
|
||||
);
|
||||
expect(resolutionTaskQueuedEvent).toBeDefined();
|
||||
|
||||
// Resolution task should exist and link back to original task
|
||||
const resolutionTaskId = (resolutionTaskQueuedEvent as TaskQueuedEvent).payload.taskId;
|
||||
const resolutionTask = await harness.taskRepository.findById(resolutionTaskId);
|
||||
expect(resolutionTask).toBeDefined();
|
||||
|
||||
// Resolution task description should contain conflict file info
|
||||
expect(resolutionTask?.description).toContain('conflict');
|
||||
});
|
||||
|
||||
it('multiple sequential conflicts resolved in order', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
||||
const taskAId = seeded.tasks.get('Task A')!;
|
||||
const taskBId = seeded.tasks.get('Task B')!;
|
||||
|
||||
// Complete both tasks
|
||||
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
||||
await harness.taskRepository.update(taskBId, { status: 'completed' });
|
||||
|
||||
// Set up worktrees and agents for both tasks
|
||||
const worktreeIdA = `wt-${taskAId.slice(0, 6)}`;
|
||||
const worktreeIdB = `wt-${taskBId.slice(0, 6)}`;
|
||||
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${taskAId.slice(0, 6)}`,
|
||||
worktreeId: worktreeIdA,
|
||||
taskId: taskAId,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${taskBId.slice(0, 6)}`,
|
||||
worktreeId: worktreeIdB,
|
||||
taskId: taskBId,
|
||||
status: 'idle',
|
||||
});
|
||||
|
||||
await harness.worktreeManager.create(worktreeIdA, 'feature-task-a');
|
||||
await harness.worktreeManager.create(worktreeIdB, 'feature-task-b');
|
||||
|
||||
// Set conflicts for both
|
||||
harness.worktreeManager.setMergeResult(worktreeIdA, {
|
||||
success: false,
|
||||
conflicts: ['src/shared-a.ts'],
|
||||
message: 'Conflict A',
|
||||
});
|
||||
harness.worktreeManager.setMergeResult(worktreeIdB, {
|
||||
success: false,
|
||||
conflicts: ['src/shared-b.ts'],
|
||||
message: 'Conflict B',
|
||||
});
|
||||
|
||||
// Queue both for merge
|
||||
await harness.coordinationManager.queueMerge(taskAId);
|
||||
await harness.coordinationManager.queueMerge(taskBId);
|
||||
harness.clearEvents();
|
||||
|
||||
// Process merges - both should fail
|
||||
const conflictResults = await harness.coordinationManager.processMerges('main');
|
||||
expect(conflictResults.filter((r) => !r.success).length).toBe(2);
|
||||
|
||||
// Verify both are in conflicted state
|
||||
const queueState = await harness.coordinationManager.getQueueState();
|
||||
expect(queueState.conflicted.length).toBe(2);
|
||||
|
||||
// Resolve Task A's conflict
|
||||
harness.worktreeManager.setMergeResult(worktreeIdA, {
|
||||
success: true,
|
||||
message: 'Merged A',
|
||||
});
|
||||
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
||||
await harness.coordinationManager.queueMerge(taskAId);
|
||||
harness.clearEvents();
|
||||
|
||||
const resultA = await harness.coordinationManager.processMerges('main');
|
||||
expect(resultA.length).toBe(1);
|
||||
expect(resultA[0].taskId).toBe(taskAId);
|
||||
expect(resultA[0].success).toBe(true);
|
||||
|
||||
// Verify merge:completed for A
|
||||
const completedEventsA = harness.getEventsByType('merge:completed');
|
||||
expect(completedEventsA.length).toBe(1);
|
||||
expect((completedEventsA[0] as MergeCompletedEvent).payload.taskId).toBe(taskAId);
|
||||
|
||||
// Resolve Task B's conflict
|
||||
harness.worktreeManager.setMergeResult(worktreeIdB, {
|
||||
success: true,
|
||||
message: 'Merged B',
|
||||
});
|
||||
await harness.taskRepository.update(taskBId, { status: 'completed' });
|
||||
await harness.coordinationManager.queueMerge(taskBId);
|
||||
harness.clearEvents();
|
||||
|
||||
const resultB = await harness.coordinationManager.processMerges('main');
|
||||
expect(resultB.length).toBe(1);
|
||||
expect(resultB[0].taskId).toBe(taskBId);
|
||||
expect(resultB[0].success).toBe(true);
|
||||
|
||||
// Verify merge:completed for B
|
||||
const completedEventsB = harness.getEventsByType('merge:completed');
|
||||
expect(completedEventsB.length).toBe(1);
|
||||
expect((completedEventsB[0] as MergeCompletedEvent).payload.taskId).toBe(taskBId);
|
||||
|
||||
// Verify final merged list has both
|
||||
const finalState = await harness.coordinationManager.getQueueState();
|
||||
expect(finalState.merged).toContain(taskAId);
|
||||
expect(finalState.merged).toContain(taskBId);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Multi-agent Parallel Work
|
||||
// ===========================================================================
|
||||
|
||||
describe('Multi-agent parallel work', () => {
|
||||
it('multiple agents complete tasks in parallel', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
|
||||
const taskXId = seeded.tasks.get('Task X')!;
|
||||
const taskYId = seeded.tasks.get('Task Y')!;
|
||||
const taskPId = seeded.tasks.get('Task P')!;
|
||||
const taskQId = seeded.tasks.get('Task Q')!;
|
||||
|
||||
// Pre-seed 3 idle agents
|
||||
await harness.agentManager.spawn({
|
||||
name: 'pool-agent-1',
|
||||
taskId: 'placeholder-1',
|
||||
prompt: 'placeholder',
|
||||
});
|
||||
await harness.agentManager.spawn({
|
||||
name: 'pool-agent-2',
|
||||
taskId: 'placeholder-2',
|
||||
prompt: 'placeholder',
|
||||
});
|
||||
await harness.agentManager.spawn({
|
||||
name: 'pool-agent-3',
|
||||
taskId: 'placeholder-3',
|
||||
prompt: 'placeholder',
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
harness.clearEvents();
|
||||
|
||||
// Queue all 4 tasks
|
||||
await harness.dispatchManager.queue(taskXId);
|
||||
await harness.dispatchManager.queue(taskYId);
|
||||
await harness.dispatchManager.queue(taskPId);
|
||||
await harness.dispatchManager.queue(taskQId);
|
||||
harness.clearEvents();
|
||||
|
||||
// Dispatch 3 tasks in parallel (3 agents working)
|
||||
const result1 = await harness.dispatchManager.dispatchNext();
|
||||
const result2 = await harness.dispatchManager.dispatchNext();
|
||||
const result3 = await harness.dispatchManager.dispatchNext();
|
||||
|
||||
expect(result1.success).toBe(true);
|
||||
expect(result2.success).toBe(true);
|
||||
expect(result3.success).toBe(true);
|
||||
|
||||
// All 3 should be dispatched to different agents
|
||||
const dispatchedIds = [result1.agentId, result2.agentId, result3.agentId];
|
||||
expect(new Set(dispatchedIds).size).toBe(3);
|
||||
|
||||
// Use vi.runAllTimersAsync() to complete all 3 agents
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Verify: 3 agent:stopped events
|
||||
const stoppedEvents = harness.getEventsByType('agent:stopped');
|
||||
expect(stoppedEvents.length).toBe(3);
|
||||
|
||||
// Complete all 3 tasks
|
||||
await harness.dispatchManager.completeTask(result1.taskId!);
|
||||
await harness.dispatchManager.completeTask(result2.taskId!);
|
||||
await harness.dispatchManager.completeTask(result3.taskId!);
|
||||
|
||||
// Dispatch remaining task (Task Q)
|
||||
const result4 = await harness.dispatchManager.dispatchNext();
|
||||
expect(result4.success).toBe(true);
|
||||
|
||||
await vi.runAllTimersAsync();
|
||||
await harness.dispatchManager.completeTask(result4.taskId!);
|
||||
|
||||
// Verify: all 4 tasks completed in database
|
||||
const tasks = await Promise.all([
|
||||
harness.taskRepository.findById(taskXId),
|
||||
harness.taskRepository.findById(taskYId),
|
||||
harness.taskRepository.findById(taskPId),
|
||||
harness.taskRepository.findById(taskQId),
|
||||
]);
|
||||
expect(tasks.every((t) => t?.status === 'completed')).toBe(true);
|
||||
});
|
||||
|
||||
it('parallel merges process in correct dependency order', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(COMPLEX_FIXTURE);
|
||||
|
||||
const task1AId = seeded.tasks.get('Task 1A')!;
|
||||
const task1BId = seeded.tasks.get('Task 1B')!;
|
||||
const task2AId = seeded.tasks.get('Task 2A')!;
|
||||
const task3AId = seeded.tasks.get('Task 3A')!;
|
||||
const task4AId = seeded.tasks.get('Task 4A')!;
|
||||
|
||||
// Complete Task 1A and Task 1B (no dependencies)
|
||||
await harness.taskRepository.update(task1AId, { status: 'completed' });
|
||||
await harness.taskRepository.update(task1BId, { status: 'completed' });
|
||||
|
||||
// Set up worktrees and agents for both
|
||||
const wt1A = `wt-${task1AId.slice(0, 6)}`;
|
||||
const wt1B = `wt-${task1BId.slice(0, 6)}`;
|
||||
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${task1AId.slice(0, 6)}`,
|
||||
worktreeId: wt1A,
|
||||
taskId: task1AId,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${task1BId.slice(0, 6)}`,
|
||||
worktreeId: wt1B,
|
||||
taskId: task1BId,
|
||||
status: 'idle',
|
||||
});
|
||||
|
||||
await harness.worktreeManager.create(wt1A, 'feature-1a');
|
||||
await harness.worktreeManager.create(wt1B, 'feature-1b');
|
||||
|
||||
// Queue both for merge
|
||||
await harness.coordinationManager.queueMerge(task1AId);
|
||||
await harness.coordinationManager.queueMerge(task1BId);
|
||||
harness.clearEvents();
|
||||
|
||||
// Process merges - both should succeed (no dependencies between them)
|
||||
const results1 = await harness.coordinationManager.processMerges('main');
|
||||
expect(results1.length).toBe(2);
|
||||
expect(results1.every((r) => r.success)).toBe(true);
|
||||
|
||||
// Verify: merge:completed for both in same batch
|
||||
const completed1 = harness.getEventsByType('merge:completed');
|
||||
expect(completed1.length).toBe(2);
|
||||
|
||||
// Complete Task 2A (depends on 1A) and Task 3A (depends on 1B)
|
||||
await harness.taskRepository.update(task2AId, { status: 'completed' });
|
||||
await harness.taskRepository.update(task3AId, { status: 'completed' });
|
||||
|
||||
const wt2A = `wt-${task2AId.slice(0, 6)}`;
|
||||
const wt3A = `wt-${task3AId.slice(0, 6)}`;
|
||||
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${task2AId.slice(0, 6)}`,
|
||||
worktreeId: wt2A,
|
||||
taskId: task2AId,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${task3AId.slice(0, 6)}`,
|
||||
worktreeId: wt3A,
|
||||
taskId: task3AId,
|
||||
status: 'idle',
|
||||
});
|
||||
|
||||
await harness.worktreeManager.create(wt2A, 'feature-2a');
|
||||
await harness.worktreeManager.create(wt3A, 'feature-3a');
|
||||
|
||||
// Queue and merge
|
||||
await harness.coordinationManager.queueMerge(task2AId);
|
||||
await harness.coordinationManager.queueMerge(task3AId);
|
||||
harness.clearEvents();
|
||||
|
||||
const results2 = await harness.coordinationManager.processMerges('main');
|
||||
expect(results2.length).toBe(2);
|
||||
expect(results2.every((r) => r.success)).toBe(true);
|
||||
|
||||
// Complete Task 4A (depends on 2A and 3A)
|
||||
await harness.taskRepository.update(task4AId, { status: 'completed' });
|
||||
|
||||
const wt4A = `wt-${task4AId.slice(0, 6)}`;
|
||||
await harness.agentRepository.create({
|
||||
name: `agent-${task4AId.slice(0, 6)}`,
|
||||
worktreeId: wt4A,
|
||||
taskId: task4AId,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.worktreeManager.create(wt4A, 'feature-4a');
|
||||
|
||||
// Queue and merge
|
||||
await harness.coordinationManager.queueMerge(task4AId);
|
||||
harness.clearEvents();
|
||||
|
||||
const results3 = await harness.coordinationManager.processMerges('main');
|
||||
expect(results3.length).toBe(1);
|
||||
expect(results3[0].taskId).toBe(task4AId);
|
||||
expect(results3[0].success).toBe(true);
|
||||
|
||||
// Verify: final merge order respects dependency graph
|
||||
const finalState = await harness.coordinationManager.getQueueState();
|
||||
expect(finalState.merged).toContain(task1AId);
|
||||
expect(finalState.merged).toContain(task1BId);
|
||||
expect(finalState.merged).toContain(task2AId);
|
||||
expect(finalState.merged).toContain(task3AId);
|
||||
expect(finalState.merged).toContain(task4AId);
|
||||
});
|
||||
|
||||
it('parallel dispatch with mixed outcomes', async () => {
|
||||
vi.useFakeTimers();
|
||||
const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
|
||||
const taskXId = seeded.tasks.get('Task X')!;
|
||||
const taskYId = seeded.tasks.get('Task Y')!;
|
||||
|
||||
// Pre-seed 2 agents
|
||||
await harness.agentManager.spawn({
|
||||
name: 'pool-agent-1',
|
||||
taskId: 'placeholder-1',
|
||||
prompt: 'placeholder',
|
||||
});
|
||||
await harness.agentManager.spawn({
|
||||
name: 'pool-agent-2',
|
||||
taskId: 'placeholder-2',
|
||||
prompt: 'placeholder',
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Set Task X to succeed, Task Y to crash
|
||||
harness.setAgentDone(`agent-${taskXId.slice(0, 6)}`, 'Task X completed');
|
||||
harness.setAgentError(`agent-${taskYId.slice(0, 6)}`, 'Out of memory error');
|
||||
|
||||
// Queue both tasks
|
||||
await harness.dispatchManager.queue(taskXId);
|
||||
await harness.dispatchManager.queue(taskYId);
|
||||
harness.clearEvents();
|
||||
|
||||
// Dispatch both tasks
|
||||
const result1 = await harness.dispatchManager.dispatchNext();
|
||||
const result2 = await harness.dispatchManager.dispatchNext();
|
||||
|
||||
// Both should dispatch successfully
|
||||
expect(result1.success).toBe(true);
|
||||
expect(result2.success).toBe(true);
|
||||
|
||||
// Run timers to complete agents
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Verify: one agent:stopped, one agent:crashed
|
||||
const stoppedEvents = harness.getEventsByType('agent:stopped');
|
||||
const crashedEvents = harness.getEventsByType('agent:crashed');
|
||||
|
||||
expect(stoppedEvents.length).toBe(1);
|
||||
expect(crashedEvents.length).toBe(1);
|
||||
|
||||
// Identify which task succeeded and which crashed
|
||||
const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
|
||||
const crashedPayload = (crashedEvents[0] as AgentCrashedEvent).payload;
|
||||
|
||||
// Find the successful task
|
||||
const successTaskId = stoppedPayload.taskId;
|
||||
const crashedTaskId = crashedPayload.taskId;
|
||||
|
||||
// Complete the successful task
|
||||
await harness.dispatchManager.completeTask(successTaskId!);
|
||||
|
||||
// Verify: completed task is actually completed
|
||||
const completedTask = await harness.taskRepository.findById(successTaskId!);
|
||||
expect(completedTask?.status).toBe('completed');
|
||||
|
||||
// Verify: crashed task stays in_progress
|
||||
const inProgressTask = await harness.taskRepository.findById(crashedTaskId!);
|
||||
expect(inProgressTask?.status).toBe('in_progress');
|
||||
|
||||
// Verify: completed task can merge (set up infrastructure)
|
||||
const wtSuccess = `wt-${successTaskId!.slice(0, 6)}`;
|
||||
await harness.agentRepository.create({
|
||||
name: `merge-agent-${successTaskId!.slice(0, 6)}`,
|
||||
worktreeId: wtSuccess,
|
||||
taskId: successTaskId!,
|
||||
status: 'idle',
|
||||
});
|
||||
await harness.worktreeManager.create(wtSuccess, 'feature-success');
|
||||
|
||||
await harness.coordinationManager.queueMerge(successTaskId!);
|
||||
const mergeResults = await harness.coordinationManager.processMerges('main');
|
||||
|
||||
expect(mergeResults.length).toBe(1);
|
||||
expect(mergeResults[0].success).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user