@@ -495,6 +495,138 @@ describe('serve app', () => {
495495 } ) ;
496496 } ) ;
497497
498+ // ── GET /api/filesystem/browse ────────────────────────────────────────
499+
500+ describe ( 'GET /api/filesystem/browse' , ( ) => {
501+ it ( 'lists child directories and marks AgentV project folders' , async ( ) => {
502+ const projectDir = path . join ( tempDir , 'project-with-agentv' ) ;
503+ const bareDir = path . join ( tempDir , 'plain-folder' ) ;
504+ mkdirSync ( path . join ( projectDir , '.agentv' ) , { recursive : true } ) ;
505+ mkdirSync ( bareDir , { recursive : true } ) ;
506+ writeFileSync ( path . join ( tempDir , 'not-a-folder.txt' ) , 'ignored' ) ;
507+
508+ const app = makeApp ( ) ;
509+ const res = await app . request ( `/api/filesystem/browse?path=${ encodeURIComponent ( tempDir ) } ` ) ;
510+
511+ expect ( res . status ) . toBe ( 200 ) ;
512+ const data = ( await res . json ( ) ) as {
513+ path : string ;
514+ parent_path ?: string ;
515+ current : { name : string ; path : string ; has_agentv : boolean } ;
516+ entries : Array < { name : string ; path : string ; has_agentv : boolean } > ;
517+ } ;
518+ expect ( data . path ) . toBe ( tempDir ) ;
519+ expect ( data . parent_path ) . toBe ( path . dirname ( tempDir ) ) ;
520+ expect ( data . current ) . toMatchObject ( {
521+ name : path . basename ( tempDir ) ,
522+ path : tempDir ,
523+ has_agentv : false ,
524+ } ) ;
525+ expect ( data . entries ) . toEqual ( [
526+ { name : 'project-with-agentv' , path : projectDir , has_agentv : true } ,
527+ { name : 'plain-folder' , path : bareDir , has_agentv : false } ,
528+ { name : 'studio-dist' , path : studioDir , has_agentv : false } ,
529+ ] ) ;
530+ } ) ;
531+
532+ it ( 'returns an understandable error for non-directory paths' , async ( ) => {
533+ const filePath = path . join ( tempDir , 'not-a-folder.txt' ) ;
534+ writeFileSync ( filePath , 'not a directory' ) ;
535+
536+ const app = makeApp ( ) ;
537+ const res = await app . request ( `/api/filesystem/browse?path=${ encodeURIComponent ( filePath ) } ` ) ;
538+
539+ expect ( res . status ) . toBe ( 400 ) ;
540+ const data = ( await res . json ( ) ) as { error : string } ;
541+ expect ( data . error ) . toContain ( 'Not a directory' ) ;
542+ expect ( data . error ) . toContain ( filePath ) ;
543+ } ) ;
544+ } ) ;
545+
546+ // ── POST /api/projects ────────────────────────────────────────────────
547+
548+ describe ( 'POST /api/projects' , ( ) => {
549+ it ( 'registers a selected AgentV project directory' , async ( ) => {
550+ const previousHome = process . env . AGENTV_HOME ;
551+ process . env . AGENTV_HOME = path . join ( tempDir , 'agentv-home-register' ) ;
552+
553+ try {
554+ const projectDir = path . join ( tempDir , 'project-to-register' ) ;
555+ mkdirSync ( path . join ( projectDir , '.agentv' ) , { recursive : true } ) ;
556+
557+ const app = makeApp ( ) ;
558+ const create = await app . request ( '/api/projects' , {
559+ method : 'POST' ,
560+ headers : { 'Content-Type' : 'application/json' } ,
561+ body : JSON . stringify ( { path : projectDir } ) ,
562+ } ) ;
563+
564+ expect ( create . status ) . toBe ( 201 ) ;
565+ const created = ( await create . json ( ) ) as {
566+ id : string ;
567+ name : string ;
568+ path : string ;
569+ added_at : string ;
570+ last_opened_at : string ;
571+ } ;
572+ expect ( created ) . toMatchObject ( {
573+ id : 'project-to-register' ,
574+ name : 'project-to-register' ,
575+ path : projectDir ,
576+ } ) ;
577+ expect ( created . added_at ) . toBeTruthy ( ) ;
578+ expect ( created . last_opened_at ) . toBeTruthy ( ) ;
579+
580+ const list = await app . request ( '/api/projects' ) ;
581+ const data = ( await list . json ( ) ) as { projects : Array < { id : string ; path : string } > } ;
582+ expect ( data . projects ) . toEqual ( [
583+ expect . objectContaining ( { id : 'project-to-register' , path : projectDir } ) ,
584+ ] ) ;
585+ } finally {
586+ if ( previousHome === undefined ) {
587+ process . env . AGENTV_HOME = undefined ;
588+ } else {
589+ process . env . AGENTV_HOME = previousHome ;
590+ }
591+ }
592+ } ) ;
593+ } ) ;
594+
595+ // ── DELETE /api/projects/:projectId ───────────────────────────────────
596+
597+ describe ( 'DELETE /api/projects/:projectId' , ( ) => {
598+ it ( 'unregisters a project without deleting its directory' , async ( ) => {
599+ const previousHome = process . env . AGENTV_HOME ;
600+ process . env . AGENTV_HOME = path . join ( tempDir , 'agentv-home-remove' ) ;
601+
602+ try {
603+ const projectDir = path . join ( tempDir , 'project-to-remove' ) ;
604+ mkdirSync ( path . join ( projectDir , '.agentv' ) , { recursive : true } ) ;
605+ const entry = addProject ( projectDir ) ;
606+
607+ const app = makeApp ( ) ;
608+ const remove = await app . request ( `/api/projects/${ encodeURIComponent ( entry . id ) } ` , {
609+ method : 'DELETE' ,
610+ } ) ;
611+
612+ expect ( remove . status ) . toBe ( 200 ) ;
613+ expect ( await remove . json ( ) ) . toEqual ( { ok : true } ) ;
614+ expect ( existsSync ( projectDir ) ) . toBe ( true ) ;
615+ expect ( existsSync ( path . join ( projectDir , '.agentv' ) ) ) . toBe ( true ) ;
616+
617+ const list = await app . request ( '/api/projects' ) ;
618+ const data = ( await list . json ( ) ) as { projects : Array < { id : string } > } ;
619+ expect ( data . projects ) . toEqual ( [ ] ) ;
620+ } finally {
621+ if ( previousHome === undefined ) {
622+ process . env . AGENTV_HOME = undefined ;
623+ } else {
624+ process . env . AGENTV_HOME = previousHome ;
625+ }
626+ }
627+ } ) ;
628+ } ) ;
629+
498630 // ── GET /api/feedback ──────────────────────────────────────────────────
499631
500632 describe ( 'GET /api/feedback' , ( ) => {
0 commit comments