feat: handler unit tests + stale test cleanup (#54)#76
Merged
Conversation
added 2 commits
March 8, 2026 17:10
…t adapters (#54) Add 18 edge-case tests covering critical untested behaviors: Worker Pool (EventDrivenWorkerPool): - Race condition: timeout fires after worker already completed - Race condition: completion fires for already-removed worker - Kill skips process.kill when process.killed is true - Timeout emit for TaskTimeout event Resource Monitor (SystemResourceMonitor): - Monitoring lifecycle: startMonitoring idempotency - Monitoring lifecycle: stopMonitoring when not monitoring - Monitoring lifecycle: no-start without eventBus - Error recovery: monitoring continues after performResourceCheck error - Settling workers: effective count tracking with constrained limits - Settling workers: expiry after 15s window - Settling workers: retained within window Agent Adapters (BaseAgentAdapter): - Kill/dispose: SIGTERM sent correctly - Kill/dispose: SIGKILL escalation after grace period - Kill/dispose: error handling (PROCESS_KILL_FAILED) - Kill/dispose: dispose clears pending kill timeouts - Kill/dispose: re-kill clears previous pending timeout Self-review fixes applied: - Strengthen weak assertions in settling workers tests (assert specific true/false values, not just result.ok) - Add missing expect(result.ok).toBe(true) guard in CPU usage data-driven tests - Remove phantom config fields (maxWorkers, maxQueueSize, spawnThrottleMs, maxEventsPerSecond) from testConfig
Add 21 new unit tests for PersistenceHandler (8), QueueHandler (9), and OutputHandler (4) — the three handlers that previously had zero test coverage. Remove 3 stale it.skip tests for unimplemented threshold events (ResourceThresholdCrossed/Recovered) that have no producer or consumer in the codebase.
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Test
participant InMemoryEventBus
participant PersistenceHandler
participant QueueHandler
participant OutputHandler
participant SQLiteTaskRepository
participant PriorityTaskQueue
Note over Test,PriorityTaskQueue: PersistenceHandler → QueueHandler flow
Test->>InMemoryEventBus: emit('TaskDelegated', {task})
InMemoryEventBus->>PersistenceHandler: handleTaskDelegated
PersistenceHandler->>SQLiteTaskRepository: save(task)
PersistenceHandler->>InMemoryEventBus: emit('TaskPersisted', {taskId, task})
InMemoryEventBus->>QueueHandler: handleTaskPersisted
QueueHandler->>SQLiteTaskRepository: dependencyRepo.isBlocked(taskId)
alt not blocked
QueueHandler->>PriorityTaskQueue: enqueue(task)
QueueHandler->>InMemoryEventBus: emit('TaskQueued', {taskId})
else blocked
QueueHandler-->>InMemoryEventBus: (no enqueue)
end
Note over Test,PriorityTaskQueue: TaskUnblocked fresh-DB-fetch flow
Test->>InMemoryEventBus: emit('TaskUnblocked', {taskId, task[P2]})
InMemoryEventBus->>QueueHandler: handleTaskUnblocked
QueueHandler->>SQLiteTaskRepository: findById(taskId) → task[P0]
QueueHandler->>PriorityTaskQueue: enqueue(task[P0])
QueueHandler->>InMemoryEventBus: emit('TaskQueued', {taskId})
Note over Test,PriorityTaskQueue: OutputHandler LogsRequested flow
Test->>InMemoryEventBus: emit('LogsRequested', {taskId, tail})
InMemoryEventBus->>OutputHandler: handleLogsRequested
OutputHandler->>OutputHandler: outputCapture.getOutput(taskId, tail)
OutputHandler-->>Test: logger.debug('Task logs retrieved', {stdoutLines, stderrLines})
Last reviewed commit: 2eba8fd |
Address PR review feedback — use TaskId() constructor instead of plain string for type consistency with other handler tests.
added 2 commits
March 9, 2026 10:16
Remove isMonitoring bracket-notation check that duplicated what the observable event assertion already validates (PR review feedback).
- Add explicit expect(spawnResult.ok).toBe(true) before guard clauses in worker pool tests to prevent silent pass on spawn failure - Add stderrLines assertion to output-handler test - Strengthen TaskUnblocked test to verify handler reads from DB (not event payload) by updating priority in DB after save
Comment on lines
+113
to
+125
| it('should update task to COMPLETED with exitCode and duration', async () => { | ||
| const task = createTask({ prompt: 'test task' }); | ||
| await taskRepo.save(task); | ||
|
|
||
| await eventBus.emit('TaskCompleted', { taskId: task.id, exitCode: 0, duration: 5000 }); | ||
| await flushEventLoop(); | ||
|
|
||
| const findResult = await taskRepo.findById(task.id); | ||
| expect(findResult.ok).toBe(true); | ||
| expect(findResult.value!.status).toBe(TaskStatus.COMPLETED); | ||
| expect(findResult.value!.exitCode).toBe(0); | ||
| expect(findResult.value!.completedAt).toBeDefined(); | ||
| }); |
There was a problem hiding this comment.
Test name promises duration assertion that never appears
The test is named "should update task to COMPLETED with exitCode and duration" and emits the event with duration: 5000, but no assertion is made on the persisted duration field. The handler at persistence-handler.ts:124 explicitly persists duration: event.duration, so a regression there would go undetected by this test.
Since Task.duration is an optional field in the domain (domain.ts:110), it can be read back from the repository. Add an assertion to match the test's stated intent:
Suggested change
| it('should update task to COMPLETED with exitCode and duration', async () => { | |
| const task = createTask({ prompt: 'test task' }); | |
| await taskRepo.save(task); | |
| await eventBus.emit('TaskCompleted', { taskId: task.id, exitCode: 0, duration: 5000 }); | |
| await flushEventLoop(); | |
| const findResult = await taskRepo.findById(task.id); | |
| expect(findResult.ok).toBe(true); | |
| expect(findResult.value!.status).toBe(TaskStatus.COMPLETED); | |
| expect(findResult.value!.exitCode).toBe(0); | |
| expect(findResult.value!.completedAt).toBeDefined(); | |
| }); | |
| it('should update task to COMPLETED with exitCode and duration', async () => { | |
| const task = createTask({ prompt: 'test task' }); | |
| await taskRepo.save(task); | |
| await eventBus.emit('TaskCompleted', { taskId: task.id, exitCode: 0, duration: 5000 }); | |
| await flushEventLoop(); | |
| const findResult = await taskRepo.findById(task.id); | |
| expect(findResult.ok).toBe(true); | |
| expect(findResult.value!.status).toBe(TaskStatus.COMPLETED); | |
| expect(findResult.value!.exitCode).toBe(0); | |
| expect(findResult.value!.completedAt).toBeDefined(); | |
| expect(findResult.value!.duration).toBe(5000); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
it.skiptests for unimplemented threshold events (ResourceThresholdCrossed/ResourceThresholdRecovered) — no type definition, producer, or consumer exists in the codebasetest:handlersscript to include the 3 new test filesCompletes Phase 2 of Issue #54. Phase 1 (18 edge-case tests for worker pool, resource monitor, agent adapters) was merged in 738e377.
Test plan
npm run build— clean compilenpm run test:handlers— 105 tests pass (84 existing + 21 new)npm run test:implementations— no regressions from skipped test removalnpm run test:services— no regressionsnpx biome check— lint clean on all modified files