test(KB-315): enhance cron-runner and worktree-pool test coverage

- Add error handling tests for cron-runner job execution
- Add stop idempotency and unknown step type tests to cron-runner
- Add startPoint parameter tests for worktree-pool
- Add worktree-pool error handling and edge case tests
This commit is contained in:
gsxdsm
2026-03-31 11:45:01 -07:00
parent d362f36a52
commit 4838090359
2 changed files with 102 additions and 1 deletions

View File

@@ -1,9 +1,31 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { CronRunner } from "./cron-runner.js";
import type { TaskStore, AutomationStore, ScheduledTask, AutomationRunResult, AutomationStep, Settings } from "@kb/core";
import { DEFAULT_SETTINGS } from "@kb/core";
import { randomUUID } from "node:crypto";
// Default settings inline to avoid @kb/core build dependency during tests
const DEFAULT_SETTINGS: Settings = {
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 30000,
autoResolveConflicts: true,
requirePlanApproval: false,
recycleWorktrees: false,
worktreeNaming: "random",
globalPause: false,
enginePaused: false,
ntfyEnabled: false,
defaultProvider: "anthropic",
defaultModelId: "claude-sonnet-4-5",
planningProvider: "anthropic",
planningModelId: "claude-sonnet-4-5",
validatorProvider: "openai",
validatorModelId: "gpt-4o",
autoUpdatePrStatus: true,
autoCreatePr: false,
taskStuckTimeoutMs: undefined,
};
function createMockSchedule(overrides: Partial<ScheduledTask> = {}): ScheduledTask {
return {
id: "test-schedule-id",
@@ -74,6 +96,15 @@ describe("CronRunner", () => {
runner.start(); // should not double-start
runner.stop();
});
it("is safe to stop when not started", () => {
const store = createMockStore();
const automationStore = createMockAutomationStore();
runner = new CronRunner(store, automationStore);
// Should not throw
expect(() => runner.stop()).not.toThrow();
});
});
describe("tick", () => {
@@ -108,6 +139,21 @@ describe("CronRunner", () => {
expect(automationStore.recordRun).not.toHaveBeenCalled();
});
it("handles errors in tick gracefully", async () => {
const store = createMockStore();
const automationStore = createMockAutomationStore([createMockSchedule()]);
// Make getDueSchedules throw an error
(automationStore.getDueSchedules as ReturnType<typeof vi.fn>).mockRejectedValue(
new Error("Database error")
);
runner = new CronRunner(store, automationStore);
// Should not throw
await expect(runner.tick()).resolves.toBeUndefined();
});
it("executes due schedules", async () => {
const store = createMockStore();
const schedule = createMockSchedule({ command: "echo test-output" });
@@ -552,6 +598,25 @@ describe("CronRunner", () => {
expect(result.stepResults![0].error).toContain("no command specified");
});
it("handles unknown step type gracefully", async () => {
const store = createMockStore();
const schedule = createMockSchedule({
command: "",
steps: [
makeStep({ name: "Unknown step", type: "unknown-type" as any }),
],
});
const automationStore = createMockAutomationStore([schedule]);
runner = new CronRunner(store, automationStore);
const result = await runner.executeSchedule(schedule);
expect(result.success).toBe(false);
expect(result.stepResults).toHaveLength(1);
expect(result.stepResults![0].success).toBe(false);
expect(result.stepResults![0].error).toContain("Unknown step type");
});
it("aggregates output from all steps with headers", async () => {
const store = createMockStore();
const schedule = createMockSchedule({