11/**
22 * Requires: npm run build prior to running Jest.
33 */
4- import { startServer , type StdioTransportClient } from './utils/stdioTransportClient' ;
5- import { loadFixture } from './utils/fixtures' ;
4+ import {
5+ startServer ,
6+ type StdioTransportClient ,
7+ type RpcRequest
8+ } from './utils/stdioTransportClient' ;
69import { setupFetchMock } from './utils/fetchMock' ;
710
811describe ( 'PatternFly MCP, STDIO' , ( ) => {
9- let client : StdioTransportClient ;
12+ let FETCH_MOCK : Awaited < ReturnType < typeof setupFetchMock > > | undefined ;
13+ let CLIENT : StdioTransportClient ;
14+ // We're unable to mock fetch for stdio since it runs in a separate process, so we run a server and use that path for mocking external URLs.
15+ let URL_MOCK : string ;
1016
11- beforeEach ( async ( ) => {
12- client = await startServer ( ) ;
17+ beforeAll ( async ( ) => {
18+ FETCH_MOCK = await setupFetchMock ( {
19+ port : 5010 ,
20+ routes : [
21+ {
22+ url : / \/ R E A D M E \. m d $ / ,
23+ // url: '/notARealPath/README.md',
24+ status : 200 ,
25+ headers : { 'Content-Type' : 'text/markdown; charset=utf-8' } ,
26+ body : `# PatternFly Development Rules
27+ This is a generated offline fixture used by the MCP external URLs test.
28+
29+ Essential rules and guidelines working with PatternFly applications.
30+
31+ ## Quick Navigation
32+
33+ ### 🚀 Setup & Environment
34+ - **Setup Rules** - Project initialization requirements
35+ - **Quick Start** - Essential setup steps
36+ - **Environment Rules** - Development configuration`
37+ } ,
38+ {
39+ url : / .* \. m d $ / ,
40+ // url: '/notARealPath/AboutModal.md',
41+ status : 200 ,
42+ headers : { 'Content-Type' : 'text/markdown; charset=utf-8' } ,
43+ body : '# Test Document\n\nThis is a test document for mocking remote HTTP requests.'
44+ }
45+ ]
46+ } ) ;
47+
48+ URL_MOCK = `${ FETCH_MOCK ?. fixture ?. baseUrl } /` ;
49+ CLIENT = await startServer ( ) ;
50+ } ) ;
51+
52+ afterAll ( async ( ) => {
53+ if ( CLIENT ) {
54+ // You may still receive jest warnings about a running process, but clean up case we forget at the test level.
55+ await CLIENT . close ( ) ;
56+ }
57+
58+ if ( FETCH_MOCK ) {
59+ await FETCH_MOCK . cleanup ( ) ;
60+ }
1361 } ) ;
1462
15- afterEach ( async ( ) => client . stop ( ) ) ;
63+ it ( 'should expose expected tools and stable shape' , async ( ) => {
64+ const response = await CLIENT . send ( {
65+ method : 'tools/list' ,
66+ params : { }
67+ } ) ;
68+ const tools = response ?. result ?. tools || [ ] ;
69+ const toolNames = tools . map ( ( tool : any ) => tool . name ) . sort ( ) ;
70+
71+ expect ( { toolNames } ) . toMatchSnapshot ( ) ;
72+ } ) ;
1673
1774 it ( 'should concatenate headers and separator with two local files' , async ( ) => {
1875 const req = {
76+ jsonrpc : '2.0' ,
77+ id : 1 ,
1978 method : 'tools/call' ,
2079 params : {
2180 name : 'usePatternFlyDocs' ,
@@ -26,33 +85,48 @@ describe('PatternFly MCP, STDIO', () => {
2685 ]
2786 }
2887 }
29- } ;
88+ } as RpcRequest ;
3089
31- const response = await client . send ( req ) ;
90+ const response = await CLIENT ? .send ( req ) ;
3291 const text = response ?. result ?. content ?. [ 0 ] ?. text || '' ;
3392
3493 expect ( text . startsWith ( '# Documentation from' ) ) . toBe ( true ) ;
3594 expect ( text ) . toMatchSnapshot ( ) ;
3695 } ) ;
3796
38- it ( 'should expose expected tools and stable shape' , async ( ) => {
39- const response = await client . send ( { method : 'tools/list' } ) ;
40- const tools = response ?. result ?. tools || [ ] ;
41- const toolNames = tools . map ( ( tool : any ) => tool . name ) . sort ( ) ;
97+ it ( 'should concatenate headers and separator with two remote files' , async ( ) => {
98+ const req = {
99+ jsonrpc : '2.0' ,
100+ id : 1 ,
101+ method : 'tools/call' ,
102+ params : {
103+ name : 'fetchDocs' ,
104+ arguments : {
105+ urlList : [
106+ // URL_MOCK
107+ `${ URL_MOCK } notARealPath/README.md` ,
108+ `${ URL_MOCK } notARealPath/AboutModal.md`
109+ ]
110+ }
111+ }
112+ } as RpcRequest ;
42113
43- expect ( toolNames ) . toEqual ( expect . arrayContaining ( [ 'usePatternFlyDocs' , 'fetchDocs' ] ) ) ;
44- expect ( { toolNames } ) . toMatchSnapshot ( ) ;
114+ const response = await CLIENT . send ( req , { timeoutMs : 10000 } ) ;
115+ const text = response ?. result ?. content ?. [ 0 ] ?. text || '' ;
116+
117+ // expect(text.startsWith('# Documentation from')).toBe(true);
118+ expect ( text ) . toMatchSnapshot ( ) ;
45119 } ) ;
46120} ) ;
47121
48122describe ( 'Hosted mode, --docs-host' , ( ) => {
49- let client : StdioTransportClient ;
123+ let CLIENT : StdioTransportClient ;
50124
51125 beforeEach ( async ( ) => {
52- client = await startServer ( { args : [ '--docs-host' ] } ) ;
126+ CLIENT = await startServer ( { args : [ '--docs-host' ] } ) ;
53127 } ) ;
54128
55- afterEach ( async ( ) => client . stop ( ) ) ;
129+ afterEach ( async ( ) => CLIENT . stop ( ) ) ;
56130
57131 it ( 'should read llms-files and includes expected tokens' , async ( ) => {
58132 const req = {
@@ -62,7 +136,7 @@ describe('Hosted mode, --docs-host', () => {
62136 arguments : { urlList : [ 'react-core/6.0.0/llms.txt' ] }
63137 }
64138 } ;
65- const resp = await client . send ( req ) ;
139+ const resp = await CLIENT . send ( req ) ;
66140 const text = resp ?. result ?. content ?. [ 0 ] ?. text || '' ;
67141
68142 expect ( text . startsWith ( '# Documentation from' ) ) . toBe ( true ) ;
@@ -81,66 +155,20 @@ describe('Logging', () => {
81155 description : 'stderr' ,
82156 args : [ '--log-stderr' ]
83157 } ,
84- {
85- description : 'verbose' ,
86- args : [ '--log-stderr' , '--verbose' ]
87- } ,
88158 {
89159 description : 'with log level filtering' ,
90160 args : [ '--log-level' , 'warn' ]
91161 } ,
92162 {
93163 description : 'with mcp protocol' ,
94- args : [ '--verbose' , '-- log-protocol']
164+ args : [ '--log-protocol' ]
95165 }
96166 ] ) ( 'should allow setting logging options, $description' , async ( { args } ) => {
97167 const serverArgs = [ ...args ] ;
98- const client = await startServer ( { args : serverArgs } ) ;
99-
100- expect ( client . logs ( ) ) . toMatchSnapshot ( ) ;
101-
102- await client . stop ( ) ;
103- } ) ;
104- } ) ;
105-
106- describe ( 'External URLs' , ( ) => {
107- let fetchMock : Awaited < ReturnType < typeof setupFetchMock > > | undefined ;
108- let url : string ;
109- let client : StdioTransportClient ;
110-
111- beforeEach ( async ( ) => {
112- client = await startServer ( ) ;
113- } ) ;
168+ const CLIENT = await startServer ( { args : serverArgs } ) ;
114169
115- afterEach ( async ( ) => client . stop ( ) ) ;
170+ expect ( CLIENT . logs ( ) ) . toMatchSnapshot ( ) ;
116171
117- beforeAll ( async ( ) => {
118- // Note: The helper creates index-based paths based on routing (/0, /1, etc.), so we use /0 for the first route
119- fetchMock = await setupFetchMock ( {
120- routes : [
121- {
122- url : / \/ r e a d m e $ / ,
123- status : 200 ,
124- headers : { 'Content-Type' : 'text/markdown; charset=utf-8' } ,
125- body : loadFixture ( 'README.md' )
126- }
127- ]
128- } ) ;
129- url = `${ fetchMock . fixture . baseUrl } /0` ;
130- } ) ;
131-
132- afterAll ( async ( ) => fetchMock ?. cleanup ( ) ) ;
133-
134- it ( 'should fetch a document' , async ( ) => {
135- const req = {
136- method : 'tools/call' ,
137- params : { name : 'fetchDocs' , arguments : { urlList : [ url ] } }
138- } ;
139- const resp = await client . send ( req , { timeoutMs : 10000 } ) ;
140- const text = resp ?. result ?. content ?. [ 0 ] ?. text || '' ;
141-
142- expect ( text . startsWith ( '# Documentation from' ) ) . toBe ( true ) ;
143- expect ( / p a t t e r n f l y / i. test ( text ) ) . toBe ( true ) ;
144- expect ( text . split ( / \n / g) . filter ( Boolean ) . splice ( 1 ) ) . toMatchSnapshot ( ) ;
172+ await CLIENT . stop ( ) ;
145173 } ) ;
146174} ) ;
0 commit comments