Files
hermes-web-ui/tests/server/workflow-store.test.ts
ekko f5a292663b [codex] Complete workflow run execution (#1759)
* feat: add workflow builder page

* Add workflow schema and editor controls

* feat: add workflow builder persistence

* feat: add workflow socket and agent skills

* feat: complete workflow run execution

* test: fix workflow ci coverage failures

---------

Co-authored-by: Codex <codex@openai.com>
2026-06-24 12:54:08 +08:00

139 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { DatabaseSync } from 'node:sqlite'
import { existsSync, mkdtempSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
const state = vi.hoisted(() => ({
db: null as DatabaseSync | null,
appHome: '',
}))
vi.mock('../../packages/server/src/db/index', () => ({
getDb: () => state.db,
jsonDelete: vi.fn(),
jsonGet: vi.fn(),
jsonGetAll: vi.fn(() => ({})),
jsonSet: vi.fn(),
}))
vi.mock('../../packages/server/src/config', () => ({
config: {
appHome: state.appHome,
},
}))
describe('workflow store', () => {
let root: string
beforeEach(async () => {
vi.resetModules()
root = mkdtempSync(join(tmpdir(), 'hermes-workflow-store-'))
state.appHome = join(root, 'home')
state.db = new DatabaseSync(join(root, 'workflow.db'))
const { initAllHermesTables } = await import('../../packages/server/src/db/hermes/schemas')
initAllHermesTables()
})
afterEach(() => {
state.db?.close()
state.db = null
rmSync(root, { recursive: true, force: true })
})
it('creates workflows with a profile-scoped default workspace', async () => {
const { createWorkflow, getWorkflow } = await import('../../packages/server/src/db/hermes/workflow-store')
const workflow = createWorkflow({
name: 'Research flow',
profile: 'research',
nodes: [{ id: 'agent-1' }],
edges: [],
viewport: { x: 12, y: 24, zoom: 0.8 },
})
expect(workflow.profile).toBe('research')
expect(workflow.workspace).toBe(join(state.appHome, 'workflow', 'research', workflow.id))
expect(existsSync(workflow.workspace!)).toBe(true)
expect(getWorkflow(workflow.id)).toMatchObject({
id: workflow.id,
name: 'Research flow',
profile: 'research',
nodes: [{ id: 'agent-1' }],
viewport: { x: 12, y: 24, zoom: 0.8 },
})
})
it('updates and deletes workflows', async () => {
const { createWorkflow, deleteWorkflow, getWorkflow, listWorkflows, updateWorkflow } = await import('../../packages/server/src/db/hermes/workflow-store')
const workflow = createWorkflow({ name: 'Draft', profile: 'default' })
const updated = updateWorkflow(workflow.id, {
name: 'Updated',
workspace: null,
nodes: [{ id: 'agent-2' }],
edges: [{ source: 'agent-1', target: 'agent-2' }],
viewport: { x: -120, y: 88, zoom: 1.1 },
})
expect(updated).toMatchObject({
id: workflow.id,
name: 'Updated',
nodes: [{ id: 'agent-2' }],
edges: [{ source: 'agent-1', target: 'agent-2' }],
viewport: { x: -120, y: 88, zoom: 1.1 },
})
expect(updated?.workspace).toBe(workflow.workspace)
expect(listWorkflows('default').map(item => item.id)).toContain(workflow.id)
expect(deleteWorkflow(workflow.id)).toBe(true)
expect(getWorkflow(workflow.id)).toBeNull()
})
it('lists workflow runs by workflow ordered newest first', async () => {
const { createWorkflow } = await import('../../packages/server/src/db/hermes/workflow-store')
const { createWorkflowRun, listWorkflowRuns, updateWorkflowRun } = await import('../../packages/server/src/db/hermes/workflow-run-store')
const workflow = createWorkflow({ name: 'Runs', profile: 'default' })
const other = createWorkflow({ name: 'Other', profile: 'default' })
const first = createWorkflowRun({ workflow_id: workflow.id, status: 'running', started_at: 100 })
await new Promise(resolve => setTimeout(resolve, 2))
const second = createWorkflowRun({ workflow_id: workflow.id, status: 'queued', started_at: 200 })
createWorkflowRun({ workflow_id: other.id, status: 'running' })
updateWorkflowRun(first.id, { status: 'completed', finished_at: 300 })
expect(listWorkflowRuns(workflow.id).map(run => run.id)).toEqual([second.id, first.id])
expect(listWorkflowRuns(workflow.id, 1)).toHaveLength(1)
expect(listWorkflowRuns(workflow.id)[1]).toMatchObject({
id: first.id,
status: 'completed',
finished_at: 300,
})
})
it('deletes workflow runs and their node session records', async () => {
const { createWorkflow } = await import('../../packages/server/src/db/hermes/workflow-store')
const {
createWorkflowRun,
createWorkflowRunNodeSession,
deleteWorkflowRun,
getWorkflowRun,
listWorkflowRunNodeSessions,
} = await import('../../packages/server/src/db/hermes/workflow-run-store')
const workflow = createWorkflow({ name: 'Runs', profile: 'default' })
const run = createWorkflowRun({ workflow_id: workflow.id, status: 'completed' })
createWorkflowRunNodeSession({
run_id: run.id,
workflow_id: workflow.id,
node_id: 'node-1',
session_id: 'session-1',
status: 'completed',
})
expect(listWorkflowRunNodeSessions(run.id)).toHaveLength(1)
expect(deleteWorkflowRun(run.id)).toBe(true)
expect(getWorkflowRun(run.id)).toBeNull()
expect(listWorkflowRunNodeSessions(run.id)).toEqual([])
})
})