import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockState = vi.hoisted(() => ({ db: null as TestDb | null, ensureSchemaCalls: 0, settings: new Map(), fetch: vi.fn(), })); vi.mock("electron-log", () => ({ default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }, })); vi.mock("../system/deviceId", () => ({ getDeviceId: vi.fn(() => "device-001"), })); vi.mock("../../db", () => ({ ensureDigitalEmployeeSchema: () => { mockState.ensureSchemaCalls += 1; return true; }, getDb: () => mockState.db, readSetting: (key: string) => mockState.settings.get(key) ?? null, })); describe("digital employee sync service", () => { beforeEach(() => { vi.resetModules(); vi.useFakeTimers(); vi.setSystemTime(new Date("2026-06-07T08:00:00.000Z")); mockState.fetch = vi.fn(); mockState.ensureSchemaCalls = 0; vi.stubGlobal("fetch", mockState.fetch); mockState.settings = new Map([ ["step1_config", { serverHost: "https://manage.example.com" }], ["auth.username", "operator"], ["auth.saved_key", "client-key-001"], ["auth.tokens.manage.example.com", "login-token-001"], ]); mockState.db = new TestDb(); }); afterEach(() => { mockState.db = null; vi.unstubAllGlobals(); vi.useRealTimers(); }); it("skips flushing when management credentials are incomplete", async () => { mockState.settings.delete("auth.saved_key"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(mockState.fetch).not.toHaveBeenCalled(); expect(status.missingCredentials).toEqual(["客户端 Key"]); expect(status.pending).toBe(1); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "pending", attempts: 0, }); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("marks entity-only acknowledgements as synced", async () => { insertPlan("plan-1"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "plan", entity_id: "plan-1", remote_id: "remote-plan-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(lastSyncRequestBody()).toMatchObject({ client_key: "client-key-001", device_id: "device-001", contract_version: "qimingclaw.digital_employee.sync.v1", items: [ expect.objectContaining({ outbox_id: "plan:upsert:plan-1", entity_type: "plan", entity_id: "plan-1", operation: "upsert", contract_version: "qimingclaw.digital_employee.sync.v1", entity_summary: { title: "同步测试计划 plan-1", status: "pending", summary: "用于验证同步失败诊断的业务摘要", }, business_view: expect.objectContaining({ entity_type: "plan", local_id: "plan-1", title: "同步测试计划 plan-1", status: "pending", objective: "用于验证同步失败诊断的业务摘要", }), payload: { ok: true }, }), ], }); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "synced", error: null, attempts: 1, }); expect(readPlan("plan-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-plan-1", sync_error: null, }); expect(readAttempts()).toEqual([ expect.objectContaining({ outbox_id: "plan:upsert:plan-1", attempt_no: 1, status: "synced", error: null, }), ]); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("accepts backend ReqResult acknowledgements", async () => { insertPlan("plan-reqresult-ok"); insertOutbox("plan:upsert:plan-reqresult-ok", "plan", "plan-reqresult-ok"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0000", message: "success", data: { acked: [ { outbox_id: "plan:upsert:plan-reqresult-ok", entity_type: "plan", entity_id: "plan-reqresult-ok", remote_id: "remote-plan-reqresult-ok", }, ], }, }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBeNull(); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-reqresult-ok")).toMatchObject({ status: "synced", error: null, attempts: 1, }); expect(readPlan("plan-reqresult-ok")).toMatchObject({ sync_status: "synced", remote_id: "remote-plan-reqresult-ok", sync_error: null, }); }); it("marks backend ReqResult business failures as failed", async () => { insertPlan("plan-rejected"); insertOutbox("plan:upsert:plan-rejected", "plan", "plan-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "客户端 Key header 缺失", data: null, }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBe("客户端 Key header 缺失"); expect(status.pending).toBe(0); expect(status.failed).toBe(1); expect(readOutbox("plan:upsert:plan-rejected")).toMatchObject({ status: "failed", error: "客户端 Key header 缺失", attempts: 1, }); expect(readPlan("plan-rejected")).toMatchObject({ sync_status: "failed", sync_error: "客户端 Key header 缺失", }); expect(readAttempts()).toEqual([ expect.objectContaining({ outbox_id: "plan:upsert:plan-rejected", attempt_no: 1, status: "failed", error: "客户端 Key header 缺失", }), ]); }); it("keeps unacknowledged rows retryable after a partial backend ack", async () => { insertPlan("plan-1"); insertPlan("plan-2"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); insertOutbox("plan:upsert:plan-2", "plan", "plan-2"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { outbox_id: "plan:upsert:plan-1", entity_type: "plan", entity_id: "plan-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBe( "management sync response did not acknowledge item", ); expect(status.failed).toBe(1); expect(status.failureSummary).toMatchObject({ total: 1, dueForRetry: 0, backoff: 1, byEntityType: [{ entityType: "plan", count: 1 }], }); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "synced", }); expect(readOutbox("plan:upsert:plan-2")).toMatchObject({ status: "failed", attempts: 1, error: "management sync response did not acknowledge item", }); expect(status.recentFailures[0]).toMatchObject({ id: "plan:upsert:plan-2", entitySummary: { title: "同步测试计划 plan-2", status: "pending", summary: "用于验证同步失败诊断的业务摘要", }, dueForRetry: false, managementRecordUrl: "https://manage.example.com/system/content/content-digital-employee-sync?device_id=device-001&entity_type=plan&entity_id=plan-2&outbox_id=plan%3Aupsert%3Aplan-2", attemptHistory: [ expect.objectContaining({ attemptNo: 1, status: "failed", error: "management sync response did not acknowledge item", endpoint: "https://manage.example.com/api/digital-employee/sync/outbox", }), ], }); }); it("marks artifact and approval acknowledgements as synced", async () => { insertEntity("artifact", "artifact-1"); insertEntity("approval", "approval-1"); insertOutbox("artifact:upsert:artifact-1", "artifact", "artifact-1"); insertOutbox("approval:upsert:approval-1", "approval", "approval-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "artifact", entity_id: "artifact-1", remote_id: "remote-artifact-1", }, { entity_type: "approval", entity_id: "approval-1", remote_id: "remote-approval-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("artifact:upsert:artifact-1")).toMatchObject({ status: "synced", attempts: 1, }); expect(readOutbox("approval:upsert:approval-1")).toMatchObject({ status: "synced", attempts: 1, }); expect(readEntity("artifact", "artifact-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-artifact-1", sync_error: null, }); expect(readEntity("approval", "approval-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-approval-1", sync_error: null, }); }); it("syncs plan step business views and acknowledgements", async () => { insertEntity("plan_step", "step-1"); insertOutbox( "plan_step:upsert:step-1", "plan_step", "step-1", JSON.stringify({ planId: "plan-1", title: "整理客户列表", status: "pending", seq: 2, dependsOn: ["step-0"], preferredSkillIds: ["qimingclaw-mcp-tool-crm-crm-search"], }), ); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "plan_step", entity_id: "step-1", remote_id: "remote-step-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(lastSyncRequestBody()).toMatchObject({ items: [ expect.objectContaining({ entity_type: "plan_step", business_view: expect.objectContaining({ entity_type: "plan_step", local_id: "step-1", plan_id: "plan-1", step_id: "step-1", title: "整理客户列表", status: "pending", seq: 2, depends_on: ["step-0"], preferred_skill_ids: ["qimingclaw-mcp-tool-crm-crm-search"], }), }), ], }); expect(status.failed).toBe(0); expect(readEntity("plan_step", "step-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-step-1", sync_error: null, }); }); it("marks plan step business failures as failed", async () => { insertEntity("plan_step", "step-rejected"); insertOutbox("plan_step:upsert:step-rejected", "plan_step", "step-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "步骤同步被拒绝", data: null }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(1); expect(readEntity("plan_step", "step-rejected")).toMatchObject({ sync_status: "failed", sync_error: "步骤同步被拒绝", }); }); it("syncs schedule and schedule run business views", async () => { insertEntity("schedule", "schedule-1"); insertEntity("schedule_run", "schedule-run-1"); insertOutbox("schedule:upsert:schedule-1", "schedule", "schedule-1"); insertOutbox("schedule_run:upsert:schedule-run-1", "schedule_run", "schedule-run-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "schedule", entity_id: "schedule-1", remote_id: "remote-schedule-1" }, { entity_type: "schedule_run", entity_id: "schedule-run-1", remote_id: "remote-schedule-run-1" }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(0); expect(lastSyncRequestBody().items).toEqual([ expect.objectContaining({ entity_type: "schedule", business_view: expect.objectContaining({ title: "同步测试计划 schedule-1", status: "pending", }), }), expect.objectContaining({ entity_type: "schedule_run", business_view: expect.objectContaining({ title: "同步测试计划 schedule-run-1", status: "pending", }), }), ]); expect(readEntity("schedule", "schedule-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-schedule-1", }); expect(readEntity("schedule_run", "schedule-run-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-schedule-run-1", }); }); it("syncs memory business views and acknowledgements", async () => { insertEntity("memory", "memory-1"); insertOutbox( "memory:upsert:memory-1", "memory", "memory-1", JSON.stringify({ key: "commit-language", content: "用户要求提交信息使用中文,并保留相关上下文。", category: "preference", source: "qimingclaw-memory-service", status: "active", }), ); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [{ entity_type: "memory", entity_id: "memory-1", remote_id: "remote-memory-1" }], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(0); expect(lastSyncRequestBody()).toMatchObject({ items: [ expect.objectContaining({ entity_type: "memory", business_view: expect.objectContaining({ entity_type: "memory", local_id: "memory-1", key: "commit-language", category: "preference", source: "qimingclaw-memory-service", status: "active", content_preview: "用户要求提交信息使用中文,并保留相关上下文。", }), }), ], }); expect(readEntity("memory", "memory-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-memory-1", sync_error: null, }); }); it("syncs specialized event business views for management consumption", async () => { insertEventEntity("event-route", "route_decision", { source: "qimingclaw-route-decision", routeDecision: { id: "route-1", route_kind: "PlanStepDispatch", intent_kind: "schedule", policy_result: "accepted", reason_codes: ["ready_plan_step"], candidate_skills: ["crm-search"], created_command_id: "governance-command-1", }, }); insertEventEntity("event-approval", "approval_action_recorded", { approval_id: "approval-1", action: "mark_risk", status: "recorded", actor: "operator", comment_summary: { preview: "存在风险", length: 4 }, delegate_summary: { target: "manager-1", label: "经理" }, sla_summary: { due_at: "2026-06-08T09:00:00.000Z", sla_status: "tracked" }, risk_summary: { risk_level: "high" }, }); insertEventEntity("event-artifact", "artifact_retention_marked", { artifact_id: "artifact-1", action: "mark_keep", status: "recorded", actor: "operator", retention_status: "kept", retention_until: "2026-07-01T00:00:00.000Z", }); insertEventEntity("event-skill", "skill_call_rejected", { call_id: "call-1", source: "qimingclaw-acp", server_id: "server-1", tool_name: "crm.search", skill_id: "crm-search", decision: "rejected", reason_codes: ["skill_disabled"], }); insertEventEntity("event-dispatch", "plan_step_dispatch_failed", { step_id: "step-1", status: "failed", reason: "computer chat failed", }); insertEventEntity("event-governance-input", "governance_provide_task_input", { commandKind: "provide_task_input", commandId: "command-provide-input", planId: "plan-1", taskId: "task-1", runId: "run-1", accepted: true, payload: { input: "请改用客户备用手机号", source: "notification_reply", notification_id: "input:task-1", plan_id: "plan-1", task_id: "task-1", }, result: { plan_id: "plan-1", task_id: "task-1", run_id: "run-1", source: "qimingclaw-task-agent", }, }); insertEventEntity("event-notification-read", "digital_workday_notification_read", { metadata: { notification_id: "task:task-1", action: "read", }, state: { notificationStateById: { "task:task-1": { status: "read", read_at: "2026-06-07T08:01:00.000Z", }, }, }, }); insertEventEntity("event-notification-dismiss", "digital_workday_notification_dismiss", { metadata: { notification_id: "task:task-2", action: "dismiss", }, state: { notificationStateById: { "task:task-2": { status: "dismissed", dismissed_at: "2026-06-07T08:02:00.000Z", }, }, }, }); insertEventEntity("event-notification-reply", "digital_workday_notification_reply", { metadata: { notification_id: "input:task-3", action: "reply", }, state: { notificationStateById: { "input:task-3": { status: "read", read_at: "2026-06-07T08:03:00.000Z", reply: "请改用客户备用手机号", replied_at: "2026-06-07T08:03:30.000Z", }, }, }, }); insertEventEntity("event-notification-preferences", "digital_workday_update_notification_preferences", { metadata: { notification_preferences: { enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], updated_at: "2026-06-07T08:04:00.000Z", }, }, state: { notificationPreferences: { enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], updated_at: "2026-06-07T08:04:00.000Z", }, }, }); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "event", entity_id: "event-route" }, { entity_type: "event", entity_id: "event-approval" }, { entity_type: "event", entity_id: "event-artifact" }, { entity_type: "event", entity_id: "event-skill" }, { entity_type: "event", entity_id: "event-dispatch" }, { entity_type: "event", entity_id: "event-governance-input" }, { entity_type: "event", entity_id: "event-notification-read" }, { entity_type: "event", entity_id: "event-notification-dismiss" }, { entity_type: "event", entity_id: "event-notification-reply" }, { entity_type: "event", entity_id: "event-notification-preferences" }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); const items = lastSyncRequestBody().items as Array<{ entity_id: string; business_view: Record }>; const businessView = (eventId: string) => items.find((item) => item.entity_id === eventId)?.business_view; expect(status.failed).toBe(0); expect(businessView("event-route")).toMatchObject({ entity_type: "event", local_id: "event-route", category: "route", kind: "route_decision", route_decision_id: "route-1", route_kind: "PlanStepDispatch", intent_kind: "schedule", policy_result: "accepted", reason_codes: ["ready_plan_step"], candidate_skills: ["crm-search"], created_command_id: "governance-command-1", attention_level: "action", }); expect(businessView("event-approval")).toMatchObject({ category: "approval", approval_id: "approval-1", action: "mark_risk", status: "recorded", actor: "operator", risk_level: "high", due_at: "2026-06-08T09:00:00.000Z", delegate_to: "manager-1", comment_length: 4, }); expect(businessView("event-artifact")).toMatchObject({ category: "artifact", artifact_id: "artifact-1", action: "mark_keep", status: "recorded", retention_status: "kept", retention_until: "2026-07-01T00:00:00.000Z", actor: "operator", }); expect(businessView("event-skill")).toMatchObject({ category: "skill", call_id: "call-1", source: "qimingclaw-acp", server_id: "server-1", tool_name: "crm.search", skill_id: "crm-search", decision: "rejected", reason_codes: ["skill_disabled"], }); expect(businessView("event-dispatch")).toMatchObject({ category: "dispatch", step_id: "step-1", status: "failed", reason: "computer chat failed", }); expect(businessView("event-governance-input")).toMatchObject({ category: "governance", command_kind: "provide_task_input", plan_id: "plan-1", task_id: "task-1", source: "notification_reply", notification_id: "input:task-1", input_length: 10, }); expect(businessView("event-governance-input")).not.toHaveProperty("input"); expect(businessView("event-notification-read")).toMatchObject({ category: "notification", notification_id: "task:task-1", action: "read", status: "read", read_at: "2026-06-07T08:01:00.000Z", }); expect(businessView("event-notification-dismiss")).toMatchObject({ category: "notification", notification_id: "task:task-2", action: "dismiss", status: "dismissed", dismissed_at: "2026-06-07T08:02:00.000Z", }); expect(businessView("event-notification-reply")).toMatchObject({ category: "notification", notification_id: "input:task-3", action: "reply", status: "read", read_at: "2026-06-07T08:03:00.000Z", replied_at: "2026-06-07T08:03:30.000Z", reply_length: 10, }); expect(businessView("event-notification-reply")).not.toHaveProperty("reply"); expect(businessView("event-notification-preferences")).toMatchObject({ category: "notification", preferences_enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], }); expect(readEntity("event", "event-route")).toMatchObject({ sync_status: "synced", sync_error: null, }); }); it("marks memory sync failures as failed", async () => { insertEntity("memory", "memory-rejected"); insertOutbox("memory:upsert:memory-rejected", "memory", "memory-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "记忆同步被拒绝", data: null }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(1); expect(readEntity("memory", "memory-rejected")).toMatchObject({ sync_status: "failed", sync_error: "记忆同步被拒绝", }); }); }); function insertPlan(id: string): void { insertEntity("plan", id); } function insertEntity(entityType: string, id: string): void { entityMap(entityType)?.set(id, { id, title: `同步测试计划 ${id}`, status: "pending", objective: "用于验证同步失败诊断的业务摘要", payload: "{}", remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, }); } function insertEventEntity(id: string, kind: string, payload: Record): void { mockState.db?.events.set(id, { id, plan_id: "plan-1", task_id: "task-1", run_id: "run-1", kind, message: `事件 ${kind}`, payload: JSON.stringify(payload), remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, occurred_at: "2026-06-07T07:59:00.000Z", }); insertOutbox(`event:insert:${id}`, "event", id, JSON.stringify({ event_id: id, kind, message: `事件 ${kind}`, plan_id: "plan-1", task_id: "task-1", occurred_at: "2026-06-07T07:59:00.000Z", }), "insert"); } function insertOutbox( id: string, entityType: string, entityId: string, payload = '{"ok":true}', operation = "upsert", ): void { mockState.db?.outbox.set(id, { id, entity_type: entityType, entity_id: entityId, operation, payload, status: "pending", attempts: 0, last_attempt_at: null, error: null, created_at: "2026-06-07T07:55:00.000Z", updated_at: "2026-06-07T07:55:00.000Z", }); } function readOutbox(id: string): Record | undefined { return mockState.db?.outbox.get(id); } function readPlan(id: string): Record | undefined { return readEntity("plan", id); } function readEntity( entityType: string, id: string, ): Record | undefined { return entityMap(entityType)?.get(id); } function entityMap( entityType: string, ): Map | undefined { if (!mockState.db) return undefined; if (entityType === "plan") return mockState.db.plans; if (entityType === "plan_step") return mockState.db.planSteps; if (entityType === "schedule") return mockState.db.schedules; if (entityType === "schedule_run") return mockState.db.scheduleRuns; if (entityType === "artifact") return mockState.db.artifacts; if (entityType === "approval") return mockState.db.approvals; if (entityType === "memory") return mockState.db.memories; if (entityType === "event") return mockState.db.events; return undefined; } function readAttempts(): Array> { return mockState.db?.attempts.map((attempt) => ({ ...attempt })) ?? []; } function jsonResponse(body: unknown): Response { return { ok: true, status: 200, text: vi.fn().mockResolvedValue(JSON.stringify(body)), } as unknown as Response; } function lastSyncRequestBody(): Record { const init = mockState.fetch.mock.calls.at(-1)?.[1] as RequestInit | undefined; return JSON.parse(String(init?.body ?? "{}")) as Record; } interface TestOutboxRow { id: string; entity_type: string; entity_id: string; operation: string; payload: string; status: string; attempts: number; last_attempt_at: string | null; error: string | null; created_at: string; updated_at: string; } interface TestSyncEntityRow { id: string; title?: string; status?: string; objective?: string | null; payload?: string; plan_id?: string | null; task_id?: string | null; run_id?: string | null; kind?: string; message?: string; occurred_at?: string; remote_id: string | null; sync_status: string; sync_error: string | null; last_synced_at: string | null; } interface TestAttemptRow { id: number; outbox_id: string; entity_type: string; entity_id: string; operation: string; attempt_no: number; status: string; error: string | null; endpoint: string | null; started_at: string; finished_at: string; } class TestDb { outbox = new Map(); plans = new Map(); planSteps = new Map(); schedules = new Map(); scheduleRuns = new Map(); artifacts = new Map(); approvals = new Map(); memories = new Map(); events = new Map(); attempts: TestAttemptRow[] = []; private nextAttemptId = 1; transaction(callback: () => T): () => T { return callback; } prepare(sql: string): { all: (...args: unknown[]) => unknown[]; get: (...args: unknown[]) => unknown; run: (...args: unknown[]) => unknown; } { const normalized = sql.replace(/\s+/g, " ").trim(); return { all: (...args: unknown[]) => this.all(normalized, args), get: (...args: unknown[]) => this.get(normalized, args), run: (...args: unknown[]) => this.run(normalized, args), }; } private all(sql: string, args: unknown[]): unknown[] { if ( sql.includes("FROM digital_sync_outbox") && sql.includes("status IN ('pending', 'failed')") ) { const limit = Number(args[0]); return Array.from(this.outbox.values()) .filter((row) => row.status === "pending" || row.status === "failed") .sort((left, right) => left.created_at.localeCompare(right.created_at)) .slice(0, limit) .map(copyRow); } if ( sql.includes("FROM digital_sync_outbox") && sql.includes("WHERE status = 'failed'") && sql.includes("SELECT entity_type") ) { return Array.from(this.outbox.values()) .filter((row) => row.status === "failed") .map((row) => ({ entity_type: row.entity_type, attempts: row.attempts, last_attempt_at: row.last_attempt_at, })); } if ( sql.includes("FROM digital_sync_outbox") && sql.includes("WHERE status = 'failed'") && sql.includes("ORDER BY COALESCE") ) { const limit = Number(args[0]); return Array.from(this.outbox.values()) .filter((row) => row.status === "failed") .sort((left, right) => recentFailureSortKey(right).localeCompare(recentFailureSortKey(left)), ) .slice(0, limit) .map(copyRow); } if (sql.includes("FROM digital_sync_attempts")) { const [outboxId, limit] = args as [string, number]; return this.attempts .filter((row) => row.outbox_id === outboxId) .sort((left, right) => { const byTime = right.finished_at.localeCompare(left.finished_at); return byTime || right.id - left.id; }) .slice(0, limit) .map((row) => ({ attempt_no: row.attempt_no, status: row.status, error: row.error, endpoint: row.endpoint, started_at: row.started_at, finished_at: row.finished_at, })); } throw new Error(`Unhandled all query: ${sql}`); } private get(sql: string, args: unknown[]): unknown { if (sql.startsWith("SELECT title, status, objective, payload FROM digital_plans")) { const [id] = args as [string]; return this.plans.get(id); } if (sql.startsWith("SELECT name AS title, kind AS status, uri AS summary, payload FROM digital_artifacts")) { const [id] = args as [string]; return this.artifacts.get(id); } if (sql.startsWith("SELECT title, status, NULL AS objective, payload FROM digital_approvals")) { const [id] = args as [string]; return this.approvals.get(id); } if (sql.startsWith("SELECT key AS title, status, content AS summary, payload FROM digital_memories")) { const [id] = args as [string]; return this.memories.get(id); } if (sql.startsWith("SELECT title, status, NULL AS objective, payload FROM digital_plan_steps")) { const [id] = args as [string]; return this.planSteps.get(id); } if (sql.startsWith("SELECT name AS title, status, description AS objective, payload FROM digital_schedules")) { const [id] = args as [string]; return this.schedules.get(id); } if (sql.startsWith("SELECT id AS title, status, error AS objective, payload FROM digital_schedule_runs")) { const [id] = args as [string]; return this.scheduleRuns.get(id); } if (sql.startsWith("SELECT kind AS title, kind AS status, message, payload FROM digital_events")) { const [id] = args as [string]; const row = this.events.get(id); if (!row) return undefined; return { title: row.kind, status: row.kind, message: row.message, payload: row.payload, }; } if (sql.startsWith("SELECT plan_id, task_id, run_id, kind, message, payload, occurred_at FROM digital_events")) { const [id] = args as [string]; return this.events.get(id); } if ( sql.includes("COUNT(*) AS count FROM digital_sync_outbox WHERE status = ?") ) { const [status] = args; return { count: Array.from(this.outbox.values()).filter( (row) => row.status === status, ).length, }; } throw new Error(`Unhandled get query: ${sql}`); } private run(sql: string, args: unknown[]): unknown { if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'syncing'")) { const [lastAttemptAt, updatedAt, id] = args as [string, string, string]; const row = this.outbox.get(id); if (row) { row.status = "syncing"; row.attempts += 1; row.last_attempt_at = lastAttemptAt; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'synced'")) { const [updatedAt, id] = args as [string, string]; const row = this.outbox.get(id); if (row) { row.status = "synced"; row.error = null; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'failed'")) { const [error, updatedAt, id] = args as [string, string, string]; const row = this.outbox.get(id); if (row) { row.status = "failed"; row.error = error; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("INSERT INTO digital_sync_attempts")) { const [ outboxId, entityType, entityId, operation, attemptNo, status, error, endpoint, startedAt, finishedAt, ] = args as [ string, string, string, string, number, string, string | null, string | null, string, string, ]; this.attempts.push({ id: this.nextAttemptId, outbox_id: outboxId, entity_type: entityType, entity_id: entityId, operation, attempt_no: attemptNo, status, error, endpoint, started_at: startedAt, finished_at: finishedAt, }); this.nextAttemptId += 1; return { changes: 1 }; } if (sql.startsWith("DELETE FROM digital_sync_attempts WHERE finished_at < ?")) { const [cutoff] = args as [string]; const before = this.attempts.length; this.attempts = this.attempts.filter((row) => row.finished_at >= cutoff); return { changes: before - this.attempts.length }; } if (/^UPDATE digital_(plans|plan_steps|schedules|schedule_runs|events|artifacts|approvals|memories) SET sync_status = 'synced'/.test(sql)) { const [remoteId, lastSyncedAt, id] = args as [string | null, string, string]; const row = this.entityRowsForUpdate(sql).get(id); if (row) { row.sync_status = "synced"; row.remote_id = remoteId ?? row.remote_id; row.last_synced_at = lastSyncedAt; row.sync_error = null; } return { changes: row ? 1 : 0 }; } if (/^UPDATE digital_(plans|plan_steps|schedules|schedule_runs|events|artifacts|approvals|memories) SET sync_status = 'failed'/.test(sql)) { const [syncError, id] = args as [string, string]; const row = this.entityRowsForUpdate(sql).get(id); if (row) { row.sync_status = "failed"; row.sync_error = syncError; } return { changes: row ? 1 : 0 }; } throw new Error(`Unhandled run query: ${sql}`); } private entityRowsForUpdate(sql: string): Map { if (sql.startsWith("UPDATE digital_artifacts")) return this.artifacts; if (sql.startsWith("UPDATE digital_approvals")) return this.approvals; if (sql.startsWith("UPDATE digital_memories")) return this.memories; if (sql.startsWith("UPDATE digital_events")) return this.events; if (sql.startsWith("UPDATE digital_schedule_runs")) return this.scheduleRuns; if (sql.startsWith("UPDATE digital_schedules")) return this.schedules; if (sql.startsWith("UPDATE digital_plan_steps")) return this.planSteps; return this.plans; } } function copyRow(row: T): T { return { ...row }; } function recentFailureSortKey(row: TestOutboxRow): string { return row.last_attempt_at || row.updated_at || row.created_at; }