test: Remove redundant and dead tests (-743 lines)

Delete 3 files:
- completion-detection.test.ts (private method tests, covered by crash-race-condition)
- completion-race-condition.test.ts (covered by mutex-completion + crash-race-condition)
- real-e2e-crash.test.ts (dead: expect(true).toBe(true), hardcoded paths)

Remove individual tests:
- crash-race-condition.test.ts #4 (weaker duplicate of #2)
- mock-manager.test.ts duplicate "(second test)" for detail_complete
- process-manager.test.ts 2 "logs comprehensive" tests with empty assertions
- edge-cases.test.ts 2 Q&A tests redundant with recovery-scenarios

Update test-inventory.md to reflect removals.
This commit is contained in:
Lukas May
2026-03-02 12:57:27 +09:00
parent a2ab4c4a84
commit 1540039c52
8 changed files with 15 additions and 743 deletions

View File

@@ -1,126 +0,0 @@
/**
* Test for completion detection via readSignalCompletion
*/
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import { mkdtemp, writeFile, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { rmSync } from 'node:fs';
import { OutputHandler } from './output-handler.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';
describe('Completion Detection Fix', () => {
let tempDir: string;
let outputHandler: OutputHandler;
let mockAgentRepo: AgentRepository;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'completion-test-'));
// Mock repositories
mockAgentRepo = {
update: vi.fn(),
findById: vi.fn().mockResolvedValue({ id: 'test-agent', mode: 'refine' }),
} as any;
outputHandler = new OutputHandler(mockAgentRepo);
});
afterEach(() => {
rmSync(tempDir, { recursive: true, force: true });
});
test('detects completion from signal.json with "questions" status', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const cwDir = join(agentWorkdir, '.cw/output');
await mkdir(cwDir, { recursive: true });
const signalContent = JSON.stringify({
status: 'questions',
questions: [{ id: 'q1', text: 'Do you want to proceed?' }]
});
await writeFile(join(cwDir, 'signal.json'), signalContent);
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).not.toBeNull();
expect(JSON.parse(result).status).toBe('questions');
});
test('detects completion from signal.json with "done" status', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const cwDir = join(agentWorkdir, '.cw/output');
await mkdir(cwDir, { recursive: true });
const signalContent = JSON.stringify({
status: 'done',
result: 'Task completed successfully'
});
await writeFile(join(cwDir, 'signal.json'), signalContent);
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).not.toBeNull();
expect(JSON.parse(result).status).toBe('done');
});
test('detects completion from signal.json with "error" status', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const cwDir = join(agentWorkdir, '.cw/output');
await mkdir(cwDir, { recursive: true });
const signalContent = JSON.stringify({
status: 'error',
error: 'Something went wrong'
});
await writeFile(join(cwDir, 'signal.json'), signalContent);
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).not.toBeNull();
expect(JSON.parse(result).status).toBe('error');
});
test('returns null when signal.json does not exist', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).toBeNull();
});
test('returns null for incomplete status', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const cwDir = join(agentWorkdir, '.cw/output');
await mkdir(cwDir, { recursive: true });
const signalContent = JSON.stringify({
status: 'running',
progress: 'Still working...'
});
await writeFile(join(cwDir, 'signal.json'), signalContent);
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).toBeNull();
});
test('handles malformed signal.json gracefully', async () => {
const agentWorkdir = join(tempDir, 'test-agent');
const cwDir = join(agentWorkdir, '.cw/output');
await mkdir(cwDir, { recursive: true });
await writeFile(join(cwDir, 'signal.json'), '{ invalid json }');
const readSignalCompletion = (outputHandler as any).readSignalCompletion.bind(outputHandler);
const result = await readSignalCompletion(agentWorkdir);
expect(result).toBeNull();
});
});

View File

@@ -1,233 +0,0 @@
/**
* Test for completion handler race condition fix.
* Verifies that only one completion handler executes per agent.
*/
import { describe, it, beforeEach, afterEach, expect } from 'vitest';
import { join } from 'node:path';
import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { OutputHandler, type ActiveAgent } from './output-handler.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';
import type { EventBus } from '../events/index.js';
// Default agent record for mocks
const defaultAgent = {
id: 'test-agent',
name: 'test-agent',
taskId: null,
provider: 'claude',
mode: 'refine' as const,
status: 'running' as const,
worktreeId: 'test-worktree',
outputFilePath: '',
sessionId: null,
result: null,
pendingQuestions: null,
initiativeId: null,
accountId: null,
userDismissedAt: null,
pid: null,
exitCode: null,
createdAt: new Date(),
updatedAt: new Date(),
};
// Mock agent repository
const mockAgentRepository: AgentRepository = {
async findById() {
return { ...defaultAgent };
},
async update(_id: string, data: any) {
return { ...defaultAgent, ...data };
},
async create() {
throw new Error('Not implemented');
},
async findAll() {
throw new Error('Not implemented');
},
async findByStatus() {
throw new Error('Not implemented');
},
async findByTaskId() {
throw new Error('Not implemented');
},
async findByName() {
throw new Error('Not implemented');
},
async findBySessionId() {
throw new Error('Not implemented');
},
async delete() {
throw new Error('Not implemented');
}
};
describe('OutputHandler completion race condition fix', () => {
let outputHandler: OutputHandler;
let testWorkdir: string;
beforeEach(async () => {
outputHandler = new OutputHandler(mockAgentRepository);
testWorkdir = await mkdtemp(join(tmpdir(), 'cw-completion-test-'));
});
afterEach(async () => {
await rm(testWorkdir, { recursive: true, force: true });
});
it('should prevent concurrent completion handling via mutex', async () => {
// Setup agent workdir with completion signal
const outputDir = join(testWorkdir, '.cw', 'output');
await mkdir(outputDir, { recursive: true });
await writeFile(join(outputDir, 'signal.json'), JSON.stringify({
status: 'questions',
questions: [{ id: 'q1', question: 'Test question?' }]
}));
const agentId = 'test-agent';
const getAgentWorkdir = (_alias: string) => testWorkdir;
// Provide a mock ActiveAgent with outputFilePath so the signal.json check path is reached
const mockActive = {
outputFilePath: join(testWorkdir, 'output.jsonl'),
streamSessionId: 'session-1'
};
// Create the output file so readCompleteLines doesn't error
await writeFile(mockActive.outputFilePath, '');
let completionCallCount = 0;
let firstHandlerStarted = false;
let secondHandlerBlocked = false;
// Track calls to the private processSignalAndFiles method
const originalProcessSignalAndFiles = (outputHandler as any).processSignalAndFiles;
(outputHandler as any).processSignalAndFiles = async (...args: any[]) => {
completionCallCount++;
if (completionCallCount === 1) {
firstHandlerStarted = true;
// Simulate some processing time
await new Promise(resolve => setTimeout(resolve, 50));
return originalProcessSignalAndFiles.apply(outputHandler, args);
} else {
// This should never be reached due to the mutex
secondHandlerBlocked = true;
return originalProcessSignalAndFiles.apply(outputHandler, args);
}
};
// Start two concurrent completion handlers
const completion1 = outputHandler.handleCompletion(agentId, mockActive as any, getAgentWorkdir);
const completion2 = outputHandler.handleCompletion(agentId, mockActive as any, getAgentWorkdir);
await Promise.all([completion1, completion2]);
// Verify mutex prevented duplicate processing
expect(firstHandlerStarted, 'First handler should have started').toBe(true);
expect(secondHandlerBlocked, 'Second handler should have been blocked by mutex').toBe(false);
expect(completionCallCount, 'Should only process completion once').toBe(1);
});
it('should handle completion when agent has questions status', async () => {
// Setup agent workdir with questions signal
const outputDir = join(testWorkdir, '.cw', 'output');
await mkdir(outputDir, { recursive: true });
await writeFile(join(outputDir, 'signal.json'), JSON.stringify({
status: 'questions',
questions: [{ id: 'q1', question: 'What should I do next?' }]
}));
const agentId = 'test-agent';
const getAgentWorkdir = (_alias: string) => testWorkdir;
// Provide a mock ActiveAgent with outputFilePath so the signal.json check path is reached
const mockActive = {
outputFilePath: join(testWorkdir, 'output.jsonl'),
streamSessionId: 'session-1'
};
await writeFile(mockActive.outputFilePath, '');
// Mock the update call to track status changes
let finalStatus: string | undefined;
(mockAgentRepository as any).update = async (id: string, updates: any) => {
if (updates.status) {
finalStatus = updates.status;
}
};
await outputHandler.handleCompletion(agentId, mockActive as any, getAgentWorkdir);
// Verify agent was marked as waiting for input, not crashed
expect(finalStatus, 'Agent should be waiting for input').toBe('waiting_for_input');
});
it('should handle completion when agent is done', async () => {
// Setup agent workdir with done signal
const outputDir = join(testWorkdir, '.cw', 'output');
await mkdir(outputDir, { recursive: true });
await writeFile(join(outputDir, 'signal.json'), JSON.stringify({
status: 'done'
}));
const agentId = 'test-agent';
const getAgentWorkdir = (_alias: string) => testWorkdir;
// Provide a mock ActiveAgent with outputFilePath so the signal.json check path is reached
const mockActive = {
outputFilePath: join(testWorkdir, 'output.jsonl'),
streamSessionId: 'session-1'
};
await writeFile(mockActive.outputFilePath, '');
// Mock the update call to track status changes
let finalStatus: string | undefined;
(mockAgentRepository as any).update = async (id: string, updates: any) => {
if (updates.status) {
finalStatus = updates.status;
}
};
await outputHandler.handleCompletion(agentId, mockActive as any, getAgentWorkdir);
// Verify agent was marked as idle, not crashed
expect(finalStatus, 'Agent should be idle after completion').toBe('idle');
});
it('should clean up completion lock even if processing fails', async () => {
const agentId = 'test-agent';
const getAgentWorkdir = (_alias: string) => testWorkdir;
// Force an error during processing
(mockAgentRepository as any).findById = async () => {
throw new Error('Database error');
};
// handleCompletion propagates errors but the finally block still cleans up the lock
await expect(outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir))
.rejects.toThrow('Database error');
// Verify lock was cleaned up by ensuring a second call can proceed
let secondCallStarted = false;
(mockAgentRepository as any).findById = async (id: string) => {
secondCallStarted = true;
return { ...defaultAgent };
};
// Create signal.json so the second call can complete successfully
const outputDir = join(testWorkdir, '.cw', 'output');
await mkdir(outputDir, { recursive: true });
await writeFile(join(outputDir, 'signal.json'), JSON.stringify({
status: 'done'
}));
const mockActive = {
outputFilePath: join(testWorkdir, 'output.jsonl'),
streamSessionId: 'session-1'
};
await writeFile(mockActive.outputFilePath, '');
await outputHandler.handleCompletion(agentId, mockActive as any, getAgentWorkdir);
expect(secondCallStarted, 'Second call should proceed after lock cleanup').toBe(true);
});
});

View File

@@ -730,25 +730,6 @@ describe('MockAgentManager', () => {
expect(agent?.status).toBe('waiting_for_input');
});
it('should emit stopped event with detail_complete reason (second test)', async () => {
manager.setScenario('detail-done', {
status: 'done',
delay: 0,
result: 'Detail complete',
});
await manager.spawn({
name: 'detail-done',
taskId: 'plan-1',
prompt: 'test',
mode: 'detail',
});
await vi.runAllTimersAsync();
const stopped = eventBus.emittedEvents.find((e) => e.type === 'agent:stopped') as AgentStoppedEvent | undefined;
expect(stopped?.payload.reason).toBe('detail_complete');
});
it('should set result message for detail mode', async () => {
manager.setScenario('detailer', {
status: 'done',

View File

@@ -159,17 +159,7 @@ describe('ProcessManager', () => {
.rejects.toThrow('Worktree creation failed:');
});
it('logs comprehensive worktree creation details', async () => {
const alias = 'test-agent';
const initiativeId = 'init-123';
await processManager.createProjectWorktrees(alias, initiativeId);
// Verify logging (implementation would need to capture log calls)
// For now, just verify the method completes successfully
expect(mockProjectRepository.findProjectsByInitiativeId).toHaveBeenCalledWith('init-123');
});
});
});
describe('createStandaloneWorktree', () => {
beforeEach(() => {
@@ -270,28 +260,6 @@ describe('ProcessManager', () => {
});
});
it('logs comprehensive spawn information', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--json-schema', 'schema.json'];
const cwd = '/test/workspace/agent-workdirs/test-agent';
const env = { CLAUDE_CONFIG_DIR: '/config' };
const providerName = 'claude';
const result = processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName);
expect(result).toHaveProperty('pid', 12345);
expect(result).toHaveProperty('outputFilePath');
expect(result).toHaveProperty('tailer');
// Verify log directory creation uses agent name, not ID
expect(mockMkdirSync).toHaveBeenCalledWith(
'/test/workspace/.cw/agent-logs/test-agent',
{ recursive: true }
);
});
it('writes prompt file when provided', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';

View File

@@ -3,7 +3,7 @@
*
* Tests edge case scenarios in dispatch/coordination flow:
* - Agent crashes during task
* - Agent waiting for input and resume
* - Agent waiting for input
* - Task blocking
* - Merge conflicts
*
@@ -20,8 +20,6 @@ import type {
AgentSpawnedEvent,
AgentCrashedEvent,
AgentWaitingEvent,
AgentResumedEvent,
AgentStoppedEvent,
TaskBlockedEvent,
MergeConflictedEvent,
} from '../../events/types.js';
@@ -171,101 +169,6 @@ describe('E2E Edge Cases', () => {
expect(waitingPayload.questions[0].question).toBe('Which database should I use?');
});
it('resumes agent and completes after resume', async () => {
vi.useFakeTimers();
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
const taskAId = seeded.tasks.get('Task A')!;
// Pre-seed required idle agent
await harness.agentManager.spawn({
name: 'pool-agent',
taskId: 'placeholder',
prompt: 'placeholder',
});
await harness.advanceTimers();
// Set questions scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
status: 'questions',
questions: [{ id: 'q1', question: 'Which database should I use?' }],
});
await harness.dispatchManager.queue(taskAId);
harness.clearEvents();
const dispatchResult = await harness.dispatchManager.dispatchNext();
await harness.advanceTimers();
// Verify agent is in waiting_for_input status
const agent = await harness.agentManager.get(dispatchResult.agentId!);
expect(agent?.status).toBe('waiting_for_input');
// Clear events to check resume events
harness.clearEvents();
// Resume agent with answers map
await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' });
await harness.advanceTimers();
// Verify: agent:resumed event emitted
const resumedEvents = harness.getEventsByType('agent:resumed');
expect(resumedEvents.length).toBe(1);
const resumedPayload = (resumedEvents[0] as AgentResumedEvent).payload;
expect(resumedPayload.taskId).toBe(taskAId);
// Verify: agent:stopped event emitted (after resume completes)
const stoppedEvents = harness.getEventsByType('agent:stopped');
expect(stoppedEvents.length).toBe(1);
const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
expect(stoppedPayload.taskId).toBe(taskAId);
expect(stoppedPayload.reason).toBe('task_complete');
});
it('agent status transitions correctly through waiting and resume', async () => {
vi.useFakeTimers();
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
const taskAId = seeded.tasks.get('Task A')!;
// Pre-seed required idle agent
await harness.agentManager.spawn({
name: 'pool-agent',
taskId: 'placeholder',
prompt: 'placeholder',
});
await harness.advanceTimers();
// Set questions scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
status: 'questions',
questions: [{ id: 'q1', question: 'Which database should I use?' }],
});
await harness.dispatchManager.queue(taskAId);
const dispatchResult = await harness.dispatchManager.dispatchNext();
// Initially running
let agent = await harness.agentManager.get(dispatchResult.agentId!);
expect(agent?.status).toBe('running');
await harness.advanceTimers();
// After scenario completes: waiting_for_input
agent = await harness.agentManager.get(dispatchResult.agentId!);
expect(agent?.status).toBe('waiting_for_input');
// Resume with answers map
await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' });
// After resume: running again
agent = await harness.agentManager.get(dispatchResult.agentId!);
expect(agent?.status).toBe('running');
await harness.advanceTimers();
// After completion: idle
agent = await harness.agentManager.get(dispatchResult.agentId!);
expect(agent?.status).toBe('idle');
});
});
describe('Task blocking', () => {

View File

@@ -229,34 +229,4 @@ Initiative page is essentially empty — lacks context, scope, goals, and techni
expect(['idle', 'waiting_for_input', 'stopped']).toContain(finalAgentStatus);
});
it('should demonstrate the race condition exists in current code', async () => {
// This test demonstrates what happens without the fix
// We'll create the conditions that trigger the race condition
// SETUP: No signal.json initially (simulates timing where signal not yet written)
const outputFilePath = join(testDir, 'output.jsonl');
await writeFile(outputFilePath, ''); // Empty output file
const mockActive = {
outputFilePath,
streamSessionId: 'session-1'
};
const getAgentWorkdir = (agentId: string) => testDir;
// EXECUTE: This should hit the "no output received" path and mark as crashed
await (outputHandler as any).handleCompletion(
testAgent.id,
mockActive,
getAgentWorkdir
);
// DOCUMENT: Show what happens in the race condition scenario
console.log('Race condition test - Final status:', finalAgentStatus);
console.log('Race condition test - Update calls:', updateCalls);
// This test shows the current behavior - it may pass or fail depending on fix
expect(updateCalls.length).toBeGreaterThan(0);
console.log('Current behavior when no signal exists:', finalAgentStatus);
});
});

View File

@@ -1,136 +0,0 @@
/**
* END-TO-END integration test with REAL Claude Code process.
* This will capture the actual crash-marking behavior with debug logs.
*/
import { describe, it, expect } from 'vitest';
import { execSync } from 'node:child_process';
import { writeFile, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { randomBytes } from 'node:crypto';
import { tmpdir } from 'node:os';
describe('Real E2E Crash Investigation', () => {
it('should spawn a real agent and capture debug logs', async () => {
if (!process.env.REAL_CLAUDE_TESTS) {
console.log('Skipping - REAL_CLAUDE_TESTS not set');
return;
}
console.log('🚀 Starting REAL end-to-end test...');
// Create test workspace
const testWorkspace = join(tmpdir(), `e2e-test-${randomBytes(4).toString('hex')}`);
await mkdir(testWorkspace, { recursive: true });
await mkdir(join(testWorkspace, '.cw/input'), { recursive: true });
// Create minimal input that will ask questions
await writeFile(join(testWorkspace, '.cw/input/manifest.json'), JSON.stringify({
files: ['initiative.md']
}));
await writeFile(join(testWorkspace, '.cw/input/initiative.md'), `---
id: test-e2e
name: E2E Test Initiative
status: active
---`);
console.log('📁 Created test workspace:', testWorkspace);
// Change to the workdir where server is running to spawn agent
const originalCwd = process.cwd();
process.chdir('/Users/lukasmay/development/projects/codewalk-district/workdir');
console.log('🎯 Spawning real Claude agent...');
console.log('📍 Working directory:', process.cwd());
const agentName = `e2e-test-${randomBytes(4).toString('hex')}`;
let spawnResult: any;
try {
// Spawn agent with shorter timeout for testing - use first available initiative
const result = execSync(`cw agent spawn --name ${agentName} --initiative 2-x-DkbSjiiRzlSAsD4ib "Please analyze the current admin UI setup and identify what needs to be replaced with shadcn + tailwind"`, {
encoding: 'utf-8',
timeout: 30000 // 30 seconds max for testing
});
console.log('📄 CLI Output:', result);
spawnResult = { success: true, output: result };
} catch (error: any) {
spawnResult = {
success: false,
exitCode: error.status,
stdout: error.stdout,
stderr: error.stderr
};
console.log('⚠️ CLI execution result:', spawnResult);
// Timeout is expected for agents that take time to complete
if (error.status === 124 || error.message?.includes('timeout')) {
console.log('⏰ Got timeout (expected) - agent was running');
}
}
// Wait a moment for completion detection to run
await new Promise(resolve => setTimeout(resolve, 2000));
// Now check the database for what happened
console.log('📊 Checking agent status in database...');
const dbPath = '.cw/cw.db'; // From workdir
try {
const agentData = execSync(
`sqlite3 "${dbPath}" "SELECT json_object('id', id, 'name', name, 'status', status, 'exit_code', exit_code, 'worktree_id', worktree_id) FROM agents WHERE name = '${agentName}' ORDER BY created_at DESC LIMIT 1;"`,
{ encoding: 'utf-8' }
).trim();
if (agentData) {
const agent = JSON.parse(agentData);
console.log('🔍 Agent found:', agent);
// Check for output files in agent workdir
const agentWorkdir = `agent-workdirs/${agentName}`;
const outputDir = join(agentWorkdir, '.cw/output');
console.log('📂 Checking output dir:', outputDir);
try {
const { existsSync } = await import('node:fs');
const hasSignal = existsSync(join(outputDir, 'signal.json'));
console.log('📄 Has signal.json:', hasSignal);
if (hasSignal) {
const { readFile } = await import('node:fs/promises');
const signalContent = await readFile(join(outputDir, 'signal.json'), 'utf-8');
const signal = JSON.parse(signalContent);
console.log('📋 Signal status:', signal.status);
if (agent.status === 'crashed' && ['questions', 'done', 'error'].includes(signal.status)) {
console.log('🚨 BUG REPRODUCED: Agent crashed despite valid completion!');
console.log('🔍 This indicates our completion detection race condition bug is still active');
} else if (agent.status === 'waiting_for_input' && signal.status === 'questions') {
console.log('✅ CORRECT: Agent properly detected completion and set waiting_for_input');
}
}
} catch (err) {
console.log('❌ Could not check output files:', err);
}
} else {
console.log('❌ No agent found in database');
}
} catch (err) {
console.log('❌ Database query failed:', err);
} finally {
// Restore original working directory
process.chdir(originalCwd);
}
// The test passes regardless - this is for investigation
expect(true).toBe(true);
}, 180000); // 3 minutes timeout
});