summaryrefslogtreecommitdiffhomepage
path: root/packages/heartbeat/src/config-store.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/heartbeat/src/config-store.test.ts')
-rw-r--r--packages/heartbeat/src/config-store.test.ts152
1 files changed, 152 insertions, 0 deletions
diff --git a/packages/heartbeat/src/config-store.test.ts b/packages/heartbeat/src/config-store.test.ts
new file mode 100644
index 0000000..9a6772d
--- /dev/null
+++ b/packages/heartbeat/src/config-store.test.ts
@@ -0,0 +1,152 @@
+import type { StorageNamespace } from "@dispatch/kernel";
+import { describe, expect, it } from "vitest";
+import {
+ applyConfigUpdate,
+ createHeartbeatConfigStore,
+ DEFAULT_HEARTBEAT_CONFIG,
+} from "./config-store.js";
+
+function createMemoryStorage(): StorageNamespace {
+ const data = new Map<string, string>();
+ return {
+ get: async (key) => data.get(key) ?? null,
+ set: async (key, value) => {
+ data.set(key, value);
+ },
+ delete: async (key) => {
+ data.delete(key);
+ },
+ has: async (key) => data.has(key),
+ keys: async (prefix) => {
+ const all = [...data.keys()];
+ if (prefix === undefined) return all;
+ return all.filter((k) => k.startsWith(prefix));
+ },
+ };
+}
+
+describe("applyConfigUpdate (pure)", () => {
+ it("leaves omitted fields unchanged from the default", () => {
+ const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, {});
+ expect(next).toEqual(DEFAULT_HEARTBEAT_CONFIG);
+ });
+
+ it("applies provided fields", () => {
+ const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, {
+ enabled: true,
+ systemPrompt: "you are a monitor",
+ taskPrompt: "check for stuck chats",
+ model: "opencode/gpt-4o",
+ });
+ expect(next.enabled).toBe(true);
+ expect(next.systemPrompt).toBe("you are a monitor");
+ expect(next.taskPrompt).toBe("check for stuck chats");
+ expect(next.model).toBe("opencode/gpt-4o");
+ // Untouched fields keep defaults.
+ expect(next.intervalMinutes).toBe(30);
+ expect(next.reasoningEffort).toBeNull();
+ });
+
+ it("clamps intervalMinutes to a minimum of 1", () => {
+ expect(
+ applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 0 }).intervalMinutes,
+ ).toBe(1);
+ expect(
+ applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: -5 }).intervalMinutes,
+ ).toBe(1);
+ expect(
+ applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 7 }).intervalMinutes,
+ ).toBe(7);
+ });
+
+ it("truncates a non-integer interval to an integer", () => {
+ expect(
+ applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 12.9 }).intervalMinutes,
+ ).toBe(12);
+ });
+
+ it("clears reasoningEffort when null is passed", () => {
+ const withEffort = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { reasoningEffort: "high" });
+ expect(withEffort.reasoningEffort).toBe("high");
+ const cleared = applyConfigUpdate(withEffort, { reasoningEffort: null });
+ expect(cleared.reasoningEffort).toBeNull();
+ });
+
+ it("treats an absent reasoningEffort as unchanged (distinct from null)", () => {
+ const withEffort = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { reasoningEffort: "low" });
+ const untouched = applyConfigUpdate(withEffort, { enabled: true });
+ expect(untouched.reasoningEffort).toBe("low");
+ });
+
+ it("defaults inactiveOnly to true (the heartbeat is quiet by default while the workspace is busy)", () => {
+ expect(DEFAULT_HEARTBEAT_CONFIG.inactiveOnly).toBe(true);
+ });
+
+ it("applies an inactiveOnly update", () => {
+ const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { inactiveOnly: false });
+ expect(next.inactiveOnly).toBe(false);
+ });
+
+ it("leaves inactiveOnly unchanged when absent from the update", () => {
+ const off = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { inactiveOnly: false });
+ const untouched = applyConfigUpdate(off, { enabled: true });
+ expect(untouched.inactiveOnly).toBe(false);
+ });
+});
+
+describe("createHeartbeatConfigStore", () => {
+ it("returns the default config for an unknown workspace", async () => {
+ const store = createHeartbeatConfigStore(createMemoryStorage());
+ expect(await store.get("ws-1")).toEqual(DEFAULT_HEARTBEAT_CONFIG);
+ });
+
+ it("persists and round-trips an update", async () => {
+ const storage = createMemoryStorage();
+ const store = createHeartbeatConfigStore(storage);
+ const next = await store.update("ws-1", { enabled: true, intervalMinutes: 5 });
+ expect(next.enabled).toBe(true);
+ expect(next.intervalMinutes).toBe(5);
+ // A fresh store over the same storage reads the persisted value.
+ const store2 = createHeartbeatConfigStore(storage);
+ expect(await store2.get("ws-1")).toEqual(next);
+ });
+
+ it("applies a partial update on top of existing config", async () => {
+ const storage = createMemoryStorage();
+ const store = createHeartbeatConfigStore(storage);
+ await store.update("ws-1", { enabled: true, systemPrompt: "a", taskPrompt: "b" });
+ const next = await store.update("ws-1", { taskPrompt: "c" });
+ expect(next.enabled).toBe(true);
+ expect(next.systemPrompt).toBe("a");
+ expect(next.taskPrompt).toBe("c");
+ });
+
+ it("round-trips inactiveOnly through persistence", async () => {
+ const storage = createMemoryStorage();
+ const store = createHeartbeatConfigStore(storage);
+ const next = await store.update("ws-1", { inactiveOnly: false });
+ expect(next.inactiveOnly).toBe(false);
+ const store2 = createHeartbeatConfigStore(storage);
+ expect((await store2.get("ws-1")).inactiveOnly).toBe(false);
+ });
+
+ it("defaults inactiveOnly to true for legacy configs persisted before the field existed", async () => {
+ const storage = createMemoryStorage();
+ // Simulate a config written by an older build that had no inactiveOnly
+ // field (a pre-inactive-only heartbeat config).
+ await storage.set("config:ws-1", JSON.stringify({ enabled: true, intervalMinutes: 5 }));
+ const store = createHeartbeatConfigStore(storage);
+ const config = await store.get("ws-1");
+ expect(config.enabled).toBe(true);
+ expect(config.intervalMinutes).toBe(5);
+ // The missing field defaults ON (feature on by default) — never `undefined`.
+ expect(config.inactiveOnly).toBe(true);
+ });
+
+ it("lists persisted workspace ids", async () => {
+ const store = createHeartbeatConfigStore(createMemoryStorage());
+ await store.update("ws-b", { enabled: true });
+ await store.update("ws-a", { enabled: false });
+ expect(await store.listWorkspaceIds()).toEqual(["ws-a", "ws-b"]);
+ });
+});