@@ -365,7 +365,7 @@ describe('AI Routes', () => {
365365 } ) ;
366366
367367 it ( 'should build all expected routes' , ( ) => {
368- const routes = buildAIRoutes ( service , silentLogger ) ;
368+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
369369 expect ( routes . length ) . toBe ( 8 ) ;
370370
371371 const paths = routes . map ( r => `${ r . method } ${ r . path } ` ) ;
@@ -380,7 +380,7 @@ describe('AI Routes', () => {
380380 } ) ;
381381
382382 it ( 'POST /api/v1/ai/chat should return chat result' , async ( ) => {
383- const routes = buildAIRoutes ( service , silentLogger ) ;
383+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
384384 const chatRoute = routes . find ( r => r . path === '/api/v1/ai/chat' ) ! ;
385385
386386 const response = await chatRoute . handler ( {
@@ -392,15 +392,15 @@ describe('AI Routes', () => {
392392 } ) ;
393393
394394 it ( 'POST /api/v1/ai/chat should return 400 without messages' , async ( ) => {
395- const routes = buildAIRoutes ( service , silentLogger ) ;
395+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
396396 const chatRoute = routes . find ( r => r . path === '/api/v1/ai/chat' ) ! ;
397397
398398 const response = await chatRoute . handler ( { body : { } } ) ;
399399 expect ( response . status ) . toBe ( 400 ) ;
400400 } ) ;
401401
402402 it ( 'POST /api/v1/ai/chat/stream should return streaming response' , async ( ) => {
403- const routes = buildAIRoutes ( service , silentLogger ) ;
403+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
404404 const streamRoute = routes . find ( r => r . path === '/api/v1/ai/chat/stream' ) ! ;
405405
406406 const response = await streamRoute . handler ( {
@@ -420,7 +420,7 @@ describe('AI Routes', () => {
420420 } ) ;
421421
422422 it ( 'POST /api/v1/ai/complete should return completion result' , async ( ) => {
423- const routes = buildAIRoutes ( service , silentLogger ) ;
423+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
424424 const completeRoute = routes . find ( r => r . path === '/api/v1/ai/complete' ) ! ;
425425
426426 const response = await completeRoute . handler ( {
@@ -432,15 +432,15 @@ describe('AI Routes', () => {
432432 } ) ;
433433
434434 it ( 'POST /api/v1/ai/complete should return 400 without prompt' , async ( ) => {
435- const routes = buildAIRoutes ( service , silentLogger ) ;
435+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
436436 const completeRoute = routes . find ( r => r . path === '/api/v1/ai/complete' ) ! ;
437437
438438 const response = await completeRoute . handler ( { body : { } } ) ;
439439 expect ( response . status ) . toBe ( 400 ) ;
440440 } ) ;
441441
442442 it ( 'GET /api/v1/ai/models should return model list' , async ( ) => {
443- const routes = buildAIRoutes ( service , silentLogger ) ;
443+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
444444 const modelsRoute = routes . find ( r => r . path === '/api/v1/ai/models' ) ! ;
445445
446446 const response = await modelsRoute . handler ( { } ) ;
@@ -449,7 +449,7 @@ describe('AI Routes', () => {
449449 } ) ;
450450
451451 it ( 'POST /api/v1/ai/conversations should create conversation' , async ( ) => {
452- const routes = buildAIRoutes ( service , silentLogger ) ;
452+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
453453 const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
454454
455455 const response = await createRoute . handler ( {
@@ -461,7 +461,7 @@ describe('AI Routes', () => {
461461 } ) ;
462462
463463 it ( 'GET /api/v1/ai/conversations should list conversations' , async ( ) => {
464- const routes = buildAIRoutes ( service , silentLogger ) ;
464+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
465465 const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
466466 const listRoute = routes . find ( r => r . method === 'GET' && r . path === '/api/v1/ai/conversations' ) ! ;
467467
@@ -474,7 +474,7 @@ describe('AI Routes', () => {
474474 } ) ;
475475
476476 it ( 'POST /api/v1/ai/conversations/:id/messages should add message' , async ( ) => {
477- const routes = buildAIRoutes ( service , silentLogger ) ;
477+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
478478 const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
479479 const addMsgRoute = routes . find ( r => r . path === '/api/v1/ai/conversations/:id/messages' ) ! ;
480480
@@ -491,7 +491,7 @@ describe('AI Routes', () => {
491491 } ) ;
492492
493493 it ( 'POST /api/v1/ai/conversations/:id/messages should return 404 for unknown conversation' , async ( ) => {
494- const routes = buildAIRoutes ( service , silentLogger ) ;
494+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
495495 const addMsgRoute = routes . find ( r => r . path === '/api/v1/ai/conversations/:id/messages' ) ! ;
496496
497497 const response = await addMsgRoute . handler ( {
@@ -503,7 +503,7 @@ describe('AI Routes', () => {
503503 } ) ;
504504
505505 it ( 'DELETE /api/v1/ai/conversations/:id should delete conversation' , async ( ) => {
506- const routes = buildAIRoutes ( service , silentLogger ) ;
506+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
507507 const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
508508 const deleteRoute = routes . find ( r => r . path === '/api/v1/ai/conversations/:id' ) ! ;
509509
@@ -513,6 +513,115 @@ describe('AI Routes', () => {
513513 const response = await deleteRoute . handler ( { params : { id : convId } } ) ;
514514 expect ( response . status ) . toBe ( 204 ) ;
515515 } ) ;
516+
517+ // ── Message validation ───────────────────────────────────────
518+
519+ it ( 'POST /api/v1/ai/chat should return 400 for messages with invalid role' , async ( ) => {
520+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
521+ const chatRoute = routes . find ( r => r . path === '/api/v1/ai/chat' ) ! ;
522+
523+ const response = await chatRoute . handler ( {
524+ body : { messages : [ { role : 'invalid' , content : 'Hi' } ] } ,
525+ } ) ;
526+
527+ expect ( response . status ) . toBe ( 400 ) ;
528+ expect ( ( response . body as any ) . error ) . toContain ( 'message.role' ) ;
529+ } ) ;
530+
531+ it ( 'POST /api/v1/ai/chat should return 400 for messages with non-string content' , async ( ) => {
532+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
533+ const chatRoute = routes . find ( r => r . path === '/api/v1/ai/chat' ) ! ;
534+
535+ const response = await chatRoute . handler ( {
536+ body : { messages : [ { role : 'user' , content : 123 } ] } ,
537+ } ) ;
538+
539+ expect ( response . status ) . toBe ( 400 ) ;
540+ expect ( ( response . body as any ) . error ) . toContain ( 'content' ) ;
541+ } ) ;
542+
543+ it ( 'POST /api/v1/ai/conversations/:id/messages should return 400 for invalid role' , async ( ) => {
544+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
545+ const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
546+ const addMsgRoute = routes . find ( r => r . path === '/api/v1/ai/conversations/:id/messages' ) ! ;
547+
548+ const created = await createRoute . handler ( { body : { } } ) ;
549+ const convId = ( created . body as any ) . id ;
550+
551+ const response = await addMsgRoute . handler ( {
552+ params : { id : convId } ,
553+ body : { role : 'invalid_role' , content : 'Hi' } ,
554+ } ) ;
555+
556+ expect ( response . status ) . toBe ( 400 ) ;
557+ expect ( ( response . body as any ) . error ) . toContain ( 'message.role' ) ;
558+ } ) ;
559+
560+ it ( 'POST /api/v1/ai/conversations/:id/messages should return 400 for missing content' , async ( ) => {
561+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
562+ const addMsgRoute = routes . find ( r => r . path === '/api/v1/ai/conversations/:id/messages' ) ! ;
563+
564+ const response = await addMsgRoute . handler ( {
565+ params : { id : 'conv_1' } ,
566+ body : { role : 'user' } ,
567+ } ) ;
568+
569+ expect ( response . status ) . toBe ( 400 ) ;
570+ expect ( ( response . body as any ) . error ) . toContain ( 'content' ) ;
571+ } ) ;
572+
573+ // ── Limit parsing ───────────────────────────────────────────
574+
575+ it ( 'GET /api/v1/ai/conversations should parse limit from query string' , async ( ) => {
576+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
577+ const createRoute = routes . find ( r => r . method === 'POST' && r . path === '/api/v1/ai/conversations' ) ! ;
578+ const listRoute = routes . find ( r => r . method === 'GET' && r . path === '/api/v1/ai/conversations' ) ! ;
579+
580+ await createRoute . handler ( { body : { title : 'C1' } } ) ;
581+ await createRoute . handler ( { body : { title : 'C2' } } ) ;
582+ await createRoute . handler ( { body : { title : 'C3' } } ) ;
583+
584+ const response = await listRoute . handler ( { query : { limit : '2' } } ) ;
585+ expect ( response . status ) . toBe ( 200 ) ;
586+ expect ( ( response . body as any ) . conversations ) . toHaveLength ( 2 ) ;
587+ } ) ;
588+
589+ it ( 'GET /api/v1/ai/conversations should return 400 for invalid limit' , async ( ) => {
590+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
591+ const listRoute = routes . find ( r => r . method === 'GET' && r . path === '/api/v1/ai/conversations' ) ! ;
592+
593+ const response = await listRoute . handler ( { query : { limit : 'abc' } } ) ;
594+ expect ( response . status ) . toBe ( 400 ) ;
595+ expect ( ( response . body as any ) . error ) . toContain ( 'limit' ) ;
596+ } ) ;
597+
598+ it ( 'GET /api/v1/ai/conversations should return 400 for negative limit' , async ( ) => {
599+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
600+ const listRoute = routes . find ( r => r . method === 'GET' && r . path === '/api/v1/ai/conversations' ) ! ;
601+
602+ const response = await listRoute . handler ( { query : { limit : '-1' } } ) ;
603+ expect ( response . status ) . toBe ( 400 ) ;
604+ expect ( ( response . body as any ) . error ) . toContain ( 'limit' ) ;
605+ } ) ;
606+
607+ // ── Tool message in chat ────────────────────────────────────
608+
609+ it ( 'POST /api/v1/ai/chat should accept tool role messages' , async ( ) => {
610+ const routes = buildAIRoutes ( service , service . conversationService , silentLogger ) ;
611+ const chatRoute = routes . find ( r => r . path === '/api/v1/ai/chat' ) ! ;
612+
613+ const response = await chatRoute . handler ( {
614+ body : {
615+ messages : [
616+ { role : 'user' , content : 'What is the weather?' } ,
617+ { role : 'assistant' , content : '' } ,
618+ { role : 'tool' , content : '{"temp": 22}' , toolCallId : 'call_1' } ,
619+ ] ,
620+ } ,
621+ } ) ;
622+
623+ expect ( response . status ) . toBe ( 200 ) ;
624+ } ) ;
516625} ) ;
517626
518627// ─────────────────────────────────────────────────────────────────
0 commit comments