@@ -941,3 +941,79 @@ describe('Epic CRUD tools', () => {
941941 expect ( tasks . every ( ( t : any ) => t . id !== ep . epicId ) ) . toBe ( true ) ;
942942 } ) ;
943943} ) ;
944+
945+ // ---------------------------------------------------------------------------
946+ // Bulk operations
947+ // ---------------------------------------------------------------------------
948+
949+ describe ( 'MCP bulk task operations' , ( ) => {
950+ let call : McpTestContext [ 'call' ] ;
951+ let close : McpTestContext [ 'close' ] ;
952+ const ids : string [ ] = [ ] ;
953+
954+ beforeAll ( async ( ) => {
955+ const embedFn = createFakeEmbed ( [ [ 'alpha' , 1 ] , [ 'beta' , 2 ] , [ 'gamma' , 3 ] ] ) ;
956+ const ctx = await setupMcpClient ( { taskGraph : createTaskGraph ( ) , embedFn } ) ;
957+ call = ctx . call ;
958+ close = ctx . close ;
959+
960+ // Create 3 tasks
961+ for ( const title of [ 'Alpha task' , 'Beta task' , 'Gamma task' ] ) {
962+ const r = json < CreateResult > ( await call ( 'tasks_create' , { title, description : 'desc' , priority : 'medium' } ) ) ;
963+ ids . push ( r . taskId ) ;
964+ }
965+ } ) ;
966+
967+ afterAll ( async ( ) => { await close ( ) ; } ) ;
968+
969+ it ( 'bulk moves tasks to a new status' , async ( ) => {
970+ const r = json < { moved : string [ ] } > ( await call ( 'tasks_bulk_move' , { taskIds : ids , status : 'in_progress' } ) ) ;
971+ expect ( r . moved ) . toHaveLength ( 3 ) ;
972+ expect ( r . moved ) . toEqual ( expect . arrayContaining ( ids ) ) ;
973+ } ) ;
974+
975+ it ( 'verifies tasks were moved' , async ( ) => {
976+ for ( const id of ids ) {
977+ const t = json < any > ( await call ( 'tasks_get' , { taskId : id } ) ) ;
978+ expect ( t . status ) . toBe ( 'in_progress' ) ;
979+ }
980+ } ) ;
981+
982+ it ( 'bulk moves with non-existent IDs — skips missing' , async ( ) => {
983+ const r = json < { moved : string [ ] } > ( await call ( 'tasks_bulk_move' , {
984+ taskIds : [ ids [ 0 ] , 'nonexistent-task' ] ,
985+ status : 'review' ,
986+ } ) ) ;
987+ expect ( r . moved ) . toHaveLength ( 1 ) ;
988+ expect ( r . moved [ 0 ] ) . toBe ( ids [ 0 ] ) ;
989+ } ) ;
990+
991+ it ( 'bulk updates priority' , async ( ) => {
992+ const r = json < { updated : string [ ] } > ( await call ( 'tasks_bulk_priority' , { taskIds : ids , priority : 'high' } ) ) ;
993+ expect ( r . updated ) . toHaveLength ( 3 ) ;
994+ } ) ;
995+
996+ it ( 'verifies priority was updated' , async ( ) => {
997+ for ( const id of ids ) {
998+ const t = json < any > ( await call ( 'tasks_get' , { taskId : id } ) ) ;
999+ expect ( t . priority ) . toBe ( 'high' ) ;
1000+ }
1001+ } ) ;
1002+
1003+ it ( 'bulk deletes tasks' , async ( ) => {
1004+ const r = json < { deleted : string [ ] } > ( await call ( 'tasks_bulk_delete' , { taskIds : [ ids [ 1 ] , ids [ 2 ] ] } ) ) ;
1005+ expect ( r . deleted ) . toHaveLength ( 2 ) ;
1006+ } ) ;
1007+
1008+ it ( 'verifies deletion' , async ( ) => {
1009+ const list = jsonList < any > ( await call ( 'tasks_list' , { } ) ) ;
1010+ expect ( list . find ( ( t : any ) => t . id === ids [ 1 ] ) ) . toBeUndefined ( ) ;
1011+ expect ( list . find ( ( t : any ) => t . id === ids [ 2 ] ) ) . toBeUndefined ( ) ;
1012+ expect ( list . find ( ( t : any ) => t . id === ids [ 0 ] ) ) . toBeDefined ( ) ;
1013+ } ) ;
1014+
1015+ it ( 'bulk delete with non-existent IDs — skips missing' , async ( ) => {
1016+ const r = json < { deleted : string [ ] } > ( await call ( 'tasks_bulk_delete' , { taskIds : [ 'nonexistent' ] } ) ) ;
1017+ expect ( r . deleted ) . toHaveLength ( 0 ) ;
1018+ } ) ;
1019+ } ) ;
0 commit comments