@@ -301,4 +301,135 @@ Content with <special> characters & symbols.
301301 expect ( fullUrl ) . toBe ( baseUrl )
302302 } )
303303 } )
304+
305+ describe ( 'get_page_content Tool' , ( ) => {
306+ it ( 'should retrieve page content successfully' , async ( ) => {
307+ const { GET } = await import ( './route' )
308+ const mockRequest = new Request ( 'https://docs.pmnd.rs/api/sse' , {
309+ method : 'POST' ,
310+ headers : {
311+ 'Content-Type' : 'application/json' ,
312+ } ,
313+ body : JSON . stringify ( {
314+ jsonrpc : '2.0' ,
315+ id : 1 ,
316+ method : 'tools/call' ,
317+ params : {
318+ name : 'get_page_content' ,
319+ arguments : {
320+ lib : 'react-three-fiber' ,
321+ path : '/api/hooks/use-frame' ,
322+ } ,
323+ } ,
324+ } ) ,
325+ } )
326+
327+ const response = await GET ( mockRequest )
328+ expect ( response ) . toBeDefined ( )
329+ } )
330+
331+ it ( 'should return error when page not found' , async ( ) => {
332+ const cheerio = await import ( 'cheerio' )
333+ const $ = cheerio . load ( mockLlmsFullTxt , { xmlMode : true } )
334+
335+ const nonExistentPath = '/non-existent-page'
336+ const page = $ ( 'page' ) . filter ( ( _ , el ) => $ ( el ) . attr ( 'path' ) === nonExistentPath )
337+
338+ expect ( page . length ) . toBe ( 0 )
339+ } )
340+
341+ it ( 'should extract correct content for valid page' , async ( ) => {
342+ const cheerio = await import ( 'cheerio' )
343+ const $ = cheerio . load ( mockLlmsFullTxt , { xmlMode : true } )
344+
345+ const targetPath = '/api/hooks/use-frame'
346+ const page = $ ( 'page' ) . filter ( ( _ , el ) => $ ( el ) . attr ( 'path' ) === targetPath )
347+
348+ expect ( page . length ) . toBe ( 1 )
349+ const content = page . text ( ) . trim ( )
350+ expect ( content ) . toContain ( 'useFrame Hook' )
351+ expect ( content ) . toContain ( 'execute code on every frame' )
352+ } )
353+
354+ it ( 'should handle multiple libraries correctly' , async ( ) => {
355+ const libs = {
356+ 'react-three-fiber' : { docs_url : 'https://r3f.docs.pmnd.rs' } ,
357+ zustand : { docs_url : 'https://zustand.docs.pmnd.rs' } ,
358+ }
359+
360+ const libNames = Object . keys ( libs )
361+ expect ( libNames ) . toContain ( 'react-three-fiber' )
362+ expect ( libNames ) . toContain ( 'zustand' )
363+ } )
364+
365+ it ( 'should validate lib parameter is enum' , async ( ) => {
366+ const { z } = await import ( 'zod' )
367+ const validLibs = [ 'react-three-fiber' , 'zustand' ]
368+ const libSchema = z . enum ( validLibs as [ string , ...string [ ] ] )
369+
370+ expect ( ( ) => libSchema . parse ( 'react-three-fiber' ) ) . not . toThrow ( )
371+ expect ( ( ) => libSchema . parse ( 'invalid-library' ) ) . toThrow ( )
372+ } )
373+
374+ it ( 'should validate path parameter is string' , async ( ) => {
375+ const { z } = await import ( 'zod' )
376+ const pathSchema = z . string ( )
377+
378+ expect ( ( ) => pathSchema . parse ( '/api/hooks/use-frame' ) ) . not . toThrow ( )
379+ expect ( ( ) => pathSchema . parse ( 123 ) ) . toThrow ( )
380+ } )
381+
382+ it ( 'should format tool response correctly' , async ( ) => {
383+ const cheerio = await import ( 'cheerio' )
384+ const $ = cheerio . load ( mockLlmsFullTxt , { xmlMode : true } )
385+
386+ const page = $ ( 'page' ) . filter ( ( _ , el ) => $ ( el ) . attr ( 'path' ) === '/getting-started' )
387+ const content = page . text ( ) . trim ( )
388+
389+ const expectedResponse = {
390+ content : [
391+ {
392+ type : 'text' ,
393+ text : content ,
394+ } ,
395+ ] ,
396+ }
397+
398+ expect ( expectedResponse . content ) . toHaveLength ( 1 )
399+ expect ( expectedResponse . content [ 0 ] . type ) . toBe ( 'text' )
400+ expect ( expectedResponse . content [ 0 ] . text ) . toContain ( 'Getting Started' )
401+ } )
402+
403+ it ( 'should handle fetch errors in tool execution' , async ( ) => {
404+ server . use (
405+ http . get ( 'https://error.docs.pmnd.rs/llms-full.txt' , ( ) => {
406+ return HttpResponse . error ( )
407+ } ) ,
408+ )
409+
410+ await expect ( fetch ( 'https://error.docs.pmnd.rs/llms-full.txt' ) ) . rejects . toThrow ( )
411+ } )
412+
413+ it ( 'should handle 404 errors in tool execution' , async ( ) => {
414+ server . use (
415+ http . get ( 'https://notfound.docs.pmnd.rs/llms-full.txt' , ( ) => {
416+ return new HttpResponse ( null , { status : 404 , statusText : 'Not Found' } )
417+ } ) ,
418+ )
419+
420+ const response = await fetch ( 'https://notfound.docs.pmnd.rs/llms-full.txt' )
421+ expect ( response . ok ) . toBe ( false )
422+ expect ( response . status ) . toBe ( 404 )
423+ } )
424+
425+ it ( 'should prevent CSS selector injection in tool' , async ( ) => {
426+ const cheerio = await import ( 'cheerio' )
427+ const $ = cheerio . load ( mockLlmsFullTxt , { xmlMode : true } )
428+
429+ const maliciousPath = '/getting-started[data-test="hack"]'
430+ const page = $ ( 'page' ) . filter ( ( _ , el ) => $ ( el ) . attr ( 'path' ) === maliciousPath )
431+
432+ expect ( page . length ) . toBe ( 0 )
433+ } )
434+ } )
304435} )
0 commit comments