Files
Hermes-ui/tests/server/kanban-routes.test.ts
T
Zhicheng Han 6ff1c18ee2 Kanban:补齐任务操作链路,明确能力边界 (#615)
* [verified] fix(kanban): harden WUI parity bridge

- Align board slug normalization with canonical underscore/lowercase/64-char rules
- Validate malformed Kanban action bodies before CLI shell-out
- Narrow task log no-log handling and expose phase-1 capabilities
- Extend client/server regression coverage for parity actions

* fix(kanban): guard archived task detail actions

---------

Co-authored-by: ekko <152005280+EKKOLearnAI@users.noreply.github.com>
2026-05-11 21:26:24 +08:00

75 lines
3.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const handlers = {
listBoards: vi.fn(async (ctx: any) => { ctx.body = { boards: [] } }),
createBoard: vi.fn(async (ctx: any) => { ctx.body = { board: {} } }),
archiveBoard: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
capabilities: vi.fn(async (ctx: any) => { ctx.body = { capabilities: {} } }),
stats: vi.fn(async (ctx: any) => { ctx.body = { stats: {} } }),
assignees: vi.fn(async (ctx: any) => { ctx.body = { assignees: [] } }),
readArtifact: vi.fn(async (ctx: any) => { ctx.body = { content: 'x' } }),
searchSessions: vi.fn(async (ctx: any) => { ctx.body = { results: [] } }),
list: vi.fn(async (ctx: any) => { ctx.body = { tasks: [] } }),
get: vi.fn(async (ctx: any) => { ctx.body = { task: {} } }),
create: vi.fn(async (ctx: any) => { ctx.body = { task: {} } }),
complete: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
unblock: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
block: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
assign: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
addComment: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
taskLog: vi.fn(async (ctx: any) => { ctx.body = { log: '' } }),
diagnostics: vi.fn(async (ctx: any) => { ctx.body = { diagnostics: [] } }),
reclaim: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
reassign: vi.fn(async (ctx: any) => { ctx.body = { ok: true } }),
specify: vi.fn(async (ctx: any) => { ctx.body = { results: [] } }),
dispatch: vi.fn(async (ctx: any) => { ctx.body = { result: {} } }),
}
vi.mock('../../packages/server/src/controllers/hermes/kanban', () => handlers)
describe('kanban routes', () => {
beforeEach(() => {
vi.resetModules()
Object.values(handlers).forEach(fn => fn.mockClear())
})
it('registers all kanban routes', async () => {
const { kanbanRoutes } = await import('../../packages/server/src/routes/hermes/kanban')
const paths = kanbanRoutes.stack.map((entry: any) => entry.path)
expect(paths).toEqual(expect.arrayContaining([
'/api/hermes/kanban/boards',
'/api/hermes/kanban/boards/:slug',
'/api/hermes/kanban/capabilities',
'/api/hermes/kanban/stats',
'/api/hermes/kanban/assignees',
'/api/hermes/kanban/diagnostics',
'/api/hermes/kanban/dispatch',
'/api/hermes/kanban/artifact',
'/api/hermes/kanban/search-sessions',
'/api/hermes/kanban',
'/api/hermes/kanban/:id',
'/api/hermes/kanban/complete',
'/api/hermes/kanban/unblock',
'/api/hermes/kanban/:id/block',
'/api/hermes/kanban/:id/assign',
'/api/hermes/kanban/:id/comments',
'/api/hermes/kanban/:id/log',
'/api/hermes/kanban/:id/reclaim',
'/api/hermes/kanban/:id/reassign',
'/api/hermes/kanban/:id/specify',
]))
})
it('delegates search-sessions to the controller', async () => {
const { kanbanRoutes } = await import('../../packages/server/src/routes/hermes/kanban')
const layer = kanbanRoutes.stack.find((entry: any) => entry.path === '/api/hermes/kanban/search-sessions')
const ctx: any = { query: { task_id: 'task-1', profile: 'alice' }, body: null, params: {} }
await layer.stack[0](ctx)
expect(handlers.searchSessions).toHaveBeenCalledWith(ctx)
expect(ctx.body).toEqual({ results: [] })
})
})