feat: add comprehensive integration tests for auto-mode-service

- Created git-test-repo helper for managing test git repositories
- Added 13 integration tests covering:
  - Worktree operations (create, error handling, non-worktree mode)
  - Feature execution (status updates, model selection, duplicate prevention)
  - Auto loop (start/stop, pending features, max concurrency, events)
  - Error handling (provider errors, continue after failures)
- Integration tests use real git operations with temporary repos
- All 416 tests passing with 72.65% overall coverage
- Service coverage improved: agent-service 58%, auto-mode-service 44%, feature-loader 66%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-13 13:34:27 +01:00
parent 0473b35db3
commit 23ff99d2e2
32 changed files with 9001 additions and 3 deletions

View File

@@ -0,0 +1,38 @@
/**
* Test helper functions
*/
/**
* Collect all values from an async generator
*/
export async function collectAsyncGenerator<T>(gen: AsyncGenerator<T>): Promise<T[]> {
const results: T[] = [];
for await (const item of gen) {
results.push(item);
}
return results;
}
/**
* Wait for a condition to be true
*/
export async function waitFor(
condition: () => boolean,
timeout = 1000,
interval = 10
): Promise<void> {
const start = Date.now();
while (!condition()) {
if (Date.now() - start > timeout) {
throw new Error("Timeout waiting for condition");
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
/**
* Create a temporary directory for tests
*/
export function createTempDir(): string {
return `/tmp/test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}

View File

@@ -0,0 +1,107 @@
/**
* Mock utilities for testing
* Provides reusable mocks for common dependencies
*/
import { vi } from "vitest";
import type { ChildProcess } from "child_process";
import { EventEmitter } from "events";
import type { Readable } from "stream";
/**
* Mock child_process.spawn for subprocess tests
*/
export function createMockChildProcess(options: {
stdout?: string[];
stderr?: string[];
exitCode?: number | null;
shouldError?: boolean;
}): ChildProcess {
const { stdout = [], stderr = [], exitCode = 0, shouldError = false } = options;
const mockProcess = new EventEmitter() as any;
// Create mock stdout stream
mockProcess.stdout = new EventEmitter() as Readable;
mockProcess.stderr = new EventEmitter() as Readable;
mockProcess.kill = vi.fn();
// Simulate async output
process.nextTick(() => {
// Emit stdout lines
for (const line of stdout) {
mockProcess.stdout.emit("data", Buffer.from(line + "\n"));
}
// Emit stderr lines
for (const line of stderr) {
mockProcess.stderr.emit("data", Buffer.from(line + "\n"));
}
// Emit exit or error
if (shouldError) {
mockProcess.emit("error", new Error("Process error"));
} else {
mockProcess.emit("exit", exitCode);
}
});
return mockProcess as ChildProcess;
}
/**
* Mock fs/promises for file system tests
*/
export function createMockFs() {
return {
readFile: vi.fn(),
writeFile: vi.fn(),
mkdir: vi.fn(),
access: vi.fn(),
stat: vi.fn(),
};
}
/**
* Mock Express request/response/next for middleware tests
*/
export function createMockExpressContext() {
const req = {
headers: {},
body: {},
params: {},
query: {},
} as any;
const res = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
} as any;
const next = vi.fn();
return { req, res, next };
}
/**
* Mock AbortController for async operation tests
*/
export function createMockAbortController() {
const controller = new AbortController();
const originalAbort = controller.abort.bind(controller);
controller.abort = vi.fn(originalAbort);
return controller;
}
/**
* Mock Claude SDK query function
*/
export function createMockClaudeQuery(messages: any[] = []) {
return vi.fn(async function* ({ prompt, options }: any) {
for (const msg of messages) {
yield msg;
}
});
}