Kanban:补齐看板事件、链接与批量操作闭环 (#634)
* feat(kanban): add board-scoped event stream bridge * test(kanban): align event refresh expectation * feat(kanban): add links and partial bulk bridge * test(kanban): align links bulk refresh expectation * fix(kanban): treat mutation stderr as failed
This commit is contained in:
@@ -13,6 +13,9 @@ const mockBlockTask = vi.hoisted(() => vi.fn())
|
||||
const mockUnblockTasks = vi.hoisted(() => vi.fn())
|
||||
const mockAssignTask = vi.hoisted(() => vi.fn())
|
||||
const mockAddComment = vi.hoisted(() => vi.fn())
|
||||
const mockLinkTasks = vi.hoisted(() => vi.fn())
|
||||
const mockUnlinkTasks = vi.hoisted(() => vi.fn())
|
||||
const mockBulkUpdateTasks = vi.hoisted(() => vi.fn())
|
||||
const mockGetTaskLog = vi.hoisted(() => vi.fn())
|
||||
const mockGetDiagnostics = vi.hoisted(() => vi.fn())
|
||||
const mockReclaimTask = vi.hoisted(() => vi.fn())
|
||||
@@ -52,6 +55,9 @@ vi.mock('../../packages/server/src/services/hermes/hermes-kanban', () => ({
|
||||
unblockTasks: mockUnblockTasks,
|
||||
assignTask: mockAssignTask,
|
||||
addComment: mockAddComment,
|
||||
linkTasks: mockLinkTasks,
|
||||
unlinkTasks: mockUnlinkTasks,
|
||||
bulkUpdateTasks: mockBulkUpdateTasks,
|
||||
getTaskLog: mockGetTaskLog,
|
||||
getDiagnostics: mockGetDiagnostics,
|
||||
reclaimTask: mockReclaimTask,
|
||||
@@ -145,6 +151,27 @@ describe('kanban controller', () => {
|
||||
expect(diagnosticsCtx.body).toEqual({ diagnostics: [{ task_id: 'task-1' }] })
|
||||
})
|
||||
|
||||
it('proxies links and bulk actions with explicit board context', async () => {
|
||||
mockLinkTasks.mockResolvedValue({ ok: true, output: 'linked' })
|
||||
mockUnlinkTasks.mockResolvedValue({ ok: true, output: 'unlinked' })
|
||||
mockBulkUpdateTasks.mockResolvedValue({ results: [{ id: 'task-1', ok: true }] })
|
||||
|
||||
const linkCtx = ctx({ query: { board: 'project-a' }, request: { body: { parent_id: 'task-1', child_id: 'task-2' } } })
|
||||
await ctrl.linkTasks(linkCtx)
|
||||
expect(mockLinkTasks).toHaveBeenCalledWith('task-1', 'task-2', { board: 'project-a' })
|
||||
expect(linkCtx.body).toEqual({ ok: true, output: 'linked' })
|
||||
|
||||
const unlinkCtx = ctx({ query: { board: 'project-a', parent_id: 'task-1', child_id: 'task-2' } })
|
||||
await ctrl.unlinkTasks(unlinkCtx)
|
||||
expect(mockUnlinkTasks).toHaveBeenCalledWith('task-1', 'task-2', { board: 'project-a' })
|
||||
expect(unlinkCtx.body).toEqual({ ok: true, output: 'unlinked' })
|
||||
|
||||
const bulkCtx = ctx({ query: { board: 'project-a' }, request: { body: { ids: ['task-1'], status: 'done', assignee: null, summary: 'closed' } } })
|
||||
await ctrl.bulkUpdateTasks(bulkCtx)
|
||||
expect(mockBulkUpdateTasks).toHaveBeenCalledWith({ board: 'project-a', ids: ['task-1'], status: 'done', assignee: null, archive: undefined, summary: 'closed', reason: undefined })
|
||||
expect(bulkCtx.body).toEqual({ results: [{ id: 'task-1', ok: true }] })
|
||||
})
|
||||
|
||||
it('validates canonical parity endpoint inputs before shelling out', async () => {
|
||||
const invalidTailCtx = ctx({ query: { board: 'default', tail: '0' }, params: { id: 'task-1' } })
|
||||
await ctrl.taskLog(invalidTailCtx)
|
||||
@@ -182,6 +209,12 @@ describe('kanban controller', () => {
|
||||
{ name: 'comment body object', invoke: ctrl.addComment, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: { body: {}, author: 'han' } } }), mock: mockAddComment },
|
||||
{ name: 'comment request body array', invoke: ctrl.addComment, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: [] } }), mock: mockAddComment },
|
||||
{ name: 'comment author object', invoke: ctrl.addComment, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: { body: 'ok', author: {} } } }), mock: mockAddComment },
|
||||
{ name: 'link missing child', invoke: ctrl.linkTasks, context: ctx({ query: { board: 'default' }, request: { body: { parent_id: 'task-1' } } }), mock: mockLinkTasks },
|
||||
{ name: 'unlink missing parent', invoke: ctrl.unlinkTasks, context: ctx({ query: { board: 'default', child_id: 'task-2' } }), mock: mockUnlinkTasks },
|
||||
{ name: 'bulk empty ids', invoke: ctrl.bulkUpdateTasks, context: ctx({ query: { board: 'default' }, request: { body: { ids: [], status: 'done' } } }), mock: mockBulkUpdateTasks },
|
||||
{ name: 'bulk invalid status', invoke: ctrl.bulkUpdateTasks, context: ctx({ query: { board: 'default' }, request: { body: { ids: ['task-1'], status: 'invalid' } } }), mock: mockBulkUpdateTasks },
|
||||
{ name: 'bulk archive with status', invoke: ctrl.bulkUpdateTasks, context: ctx({ query: { board: 'default' }, request: { body: { ids: ['task-1'], archive: true, status: 'done' } } }), mock: mockBulkUpdateTasks },
|
||||
{ name: 'bulk no action', invoke: ctrl.bulkUpdateTasks, context: ctx({ query: { board: 'default' }, request: { body: { ids: ['task-1'] } } }), mock: mockBulkUpdateTasks },
|
||||
{ name: 'reclaim request body string', invoke: ctrl.reclaim, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: 'bad' } }), mock: mockReclaimTask },
|
||||
{ name: 'reclaim reason array', invoke: ctrl.reclaim, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: { reason: [] } } }), mock: mockReclaimTask },
|
||||
{ name: 'reassign reclaim string', invoke: ctrl.reassign, context: ctx({ query: { board: 'default' }, params: { id: 'task-1' }, request: { body: { profile: 'bob', reclaim: 'false' } } }), mock: mockReassignTask },
|
||||
|
||||
Reference in New Issue
Block a user