@@ -82,31 +82,6 @@ function mockMCPTokenCreation(token: string): void {
8282// Helpers for table-driven per-editor tests
8383// ---------------------------------------------------------------------------
8484
85- interface EditorTestCase {
86- /** How to make this editor detectable */
87- detect : {
88- /** CLI commands that should succeed (e.g. ['codex', 'claude']) */
89- cliCommands ?: string [ ]
90- /** Env vars to set for this test */
91- env ?: Record < string , string >
92- /** existsSync predicate — receives the path string */
93- existsSync ?: ( p : string ) => boolean
94- /** Platform to override via Object.defineProperty */
95- overridePlatform ?: NodeJS . Platform
96- }
97- /** Substring the written config path must contain */
98- expectedConfigPath : string
99- /** Editor name as it appears in EDITOR_CONFIGS */
100- name : string
101-
102- /** Extra substrings the written content must contain (beyond the token) */
103- expectedContentChecks ?: string [ ]
104- /** If true, this editor uses OAuth natively and does not need an embedded API token */
105- oauthOnly ?: boolean
106- /** Only run on this platform (skipped otherwise) */
107- platform ?: NodeJS . Platform
108- }
109-
11085const EXECA_SUCCESS = {
11186 command : 'test --version' ,
11287 exitCode : 0 ,
@@ -118,294 +93,6 @@ const EXECA_SUCCESS = {
11893 timedOut : false ,
11994} as never
12095
121- /** Shared helper: sets up mocks, runs command, asserts outputs for a single editor. */
122- async function runEditorTest ( tc : EditorTestCase ) : Promise < void > {
123- const originalPlatform = process . platform
124- const envBackups : Record < string , string | undefined > = { }
125-
126- try {
127- // Platform override
128- if ( tc . detect . overridePlatform ) {
129- Object . defineProperty ( process , 'platform' , { value : tc . detect . overridePlatform } )
130- }
131-
132- // Env var overrides
133- if ( tc . detect . env ) {
134- for ( const [ key , value ] of Object . entries ( tc . detect . env ) ) {
135- envBackups [ key ] = process . env [ key ]
136- process . env [ key ] = value
137- }
138- }
139-
140- // existsSync mock
141- if ( tc . detect . existsSync ) {
142- const predicate = tc . detect . existsSync
143- mockExistsSync . mockImplementation ( ( p : PathLike ) => predicate ( String ( p ) ) )
144- }
145-
146- // CLI mock — resolve for specific commands, reject for everything else
147- if ( tc . detect . cliCommands ) {
148- const commands = tc . detect . cliCommands
149- mockExeca . mockImplementation ( ( async ( command : string | URL ) => {
150- if ( commands . includes ( String ( command ) ) ) return EXECA_SUCCESS
151- throw new Error ( 'Not installed' )
152- } ) as never )
153- }
154-
155- mockCheckbox . mockResolvedValue ( [ tc . name ] )
156-
157- const token = `test-token-${ tc . name . toLowerCase ( ) . replaceAll ( / \s + / g, '-' ) } `
158-
159- if ( ! tc . oauthOnly ) {
160- mockMCPTokenCreation ( token )
161- }
162-
163- const { stdout} = await testCommand ( ConfigureMcpCommand , [ ] )
164-
165- // Assert config was written to the expected path with the token
166- expect ( mockWriteFile ) . toHaveBeenCalledWith (
167- expect . stringContaining ( convertToSystemPath ( tc . expectedConfigPath ) ) ,
168- tc . oauthOnly ? expect . not . stringContaining ( token ) : expect . stringContaining ( token ) ,
169- 'utf8' ,
170- )
171-
172- // Assert extra content checks
173- if ( tc . expectedContentChecks ) {
174- const writtenContent = mockWriteFile . mock . calls [ 0 ] ?. [ 1 ] as string
175- for ( const check of tc . expectedContentChecks ) {
176- expect ( writtenContent , `written content should contain "${ check } "` ) . toContain ( check )
177- }
178- }
179-
180- expect ( stdout ) . toContain ( `MCP configured for ${ tc . name } ` )
181- } finally {
182- // Restore platform
183- if ( tc . detect . overridePlatform ) {
184- Object . defineProperty ( process , 'platform' , { value : originalPlatform } )
185- }
186- // Restore env vars
187- for ( const [ key , original ] of Object . entries ( envBackups ) ) {
188- if ( original === undefined ) {
189- delete process . env [ key ]
190- } else {
191- process . env [ key ] = original
192- }
193- }
194- }
195- }
196-
197- // ---------------------------------------------------------------------------
198- // Test cases — one entry per editor/variant
199- // ---------------------------------------------------------------------------
200-
201- const editorTestCases : EditorTestCase [ ] = [
202- {
203- detect : { existsSync : ( p ) => p . endsWith ( '.cursor' ) } ,
204- expectedConfigPath : '.cursor/mcp.json' ,
205- name : 'Cursor' ,
206- oauthOnly : true ,
207- } ,
208- {
209- detect : {
210- existsSync : ( p ) => p . endsWith ( 'Code/User' ) ,
211- overridePlatform : 'darwin' ,
212- } ,
213- expectedConfigPath : 'Code/User/mcp.json' ,
214- name : 'VS Code' ,
215- platform : 'darwin' ,
216- } ,
217- {
218- detect : {
219- env : { APPDATA : String . raw `C:\Users\test\AppData\Roaming` } ,
220- existsSync : ( p ) => p . includes ( String . raw `AppData\Roaming\Code\User` ) ,
221- overridePlatform : 'win32' ,
222- } ,
223- expectedConfigPath : String . raw `AppData\Roaming\Code\User\mcp.json` ,
224- name : 'VS Code' ,
225- platform : 'win32' ,
226- } ,
227- {
228- detect : {
229- existsSync : ( p ) => p . endsWith ( 'Code - Insiders/User' ) ,
230- overridePlatform : 'darwin' ,
231- } ,
232- expectedConfigPath : 'Code - Insiders/User/mcp.json' ,
233- name : 'VS Code Insiders' ,
234- platform : 'darwin' ,
235- } ,
236- {
237- detect : {
238- env : { APPDATA : String . raw `C:\Users\test\AppData\Roaming` } ,
239- existsSync : ( p ) => p . includes ( String . raw `AppData\Roaming\Code - Insiders\User` ) ,
240- overridePlatform : 'win32' ,
241- } ,
242- expectedConfigPath : String . raw `AppData\Roaming\Code - Insiders\User\mcp.json` ,
243- name : 'VS Code Insiders' ,
244- platform : 'win32' ,
245- } ,
246- {
247- detect : { cliCommands : [ 'claude' ] } ,
248- expectedConfigPath : '.claude.json' ,
249- name : 'Claude Code' ,
250- oauthOnly : true ,
251- } ,
252- {
253- detect : {
254- existsSync : ( p ) => {
255- const n = p . replaceAll ( '\\' , '/' )
256- return n . endsWith ( '/.gemini/antigravity' )
257- } ,
258- } ,
259- expectedConfigPath : '.gemini/antigravity/mcp_config.json' ,
260- expectedContentChecks : [ 'serverUrl' ] ,
261- name : 'Antigravity' ,
262- } ,
263- {
264- detect : {
265- existsSync : ( p ) => {
266- const n = p . replaceAll ( '\\' , '/' )
267- return n . endsWith ( '/Code/User/globalStorage/saoudrizwan.claude-dev/settings' )
268- } ,
269- } ,
270- expectedConfigPath :
271- 'Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' ,
272- name : 'Cline' ,
273- } ,
274- {
275- detect : {
276- env : {
277- CLINE_DIR :
278- process . platform === 'win32'
279- ? String . raw `C:\tmp\custom-cline-home`
280- : '/tmp/custom-cline-home' ,
281- } ,
282- existsSync : ( p ) => {
283- const n = p . replaceAll ( '\\' , '/' )
284- return n . endsWith ( '/tmp/custom-cline-home' )
285- } ,
286- } ,
287- expectedConfigPath : convertToSystemPath (
288- '/tmp/custom-cline-home/data/settings/cline_mcp_settings.json' ,
289- ) ,
290- name : 'Cline CLI' ,
291- } ,
292- {
293- detect : {
294- existsSync : ( p ) => {
295- const n = p . replaceAll ( '\\' , '/' )
296- return n . endsWith ( '/.gemini/settings.json' )
297- } ,
298- } ,
299- expectedConfigPath : '.gemini/settings.json' ,
300- name : 'Gemini CLI' ,
301- } ,
302- {
303- detect : {
304- existsSync : ( p ) => {
305- const n = p . replaceAll ( '\\' , '/' )
306- return / \/ .? c o p i l o t (?: \/ | $ ) / . test ( n )
307- } ,
308- } ,
309- expectedConfigPath : 'mcp-config.json' ,
310- expectedContentChecks : [ '"tools"' ] ,
311- name : 'GitHub Copilot CLI' ,
312- } ,
313- {
314- detect : {
315- env : { XDG_CONFIG_HOME : '/home/user/.config' } ,
316- existsSync : ( p ) => p . includes ( '/home/user/.config/copilot' ) ,
317- } ,
318- expectedConfigPath : '/home/user/.config/copilot/mcp-config.json' ,
319- name : 'GitHub Copilot CLI' ,
320- platform : 'linux' ,
321- } ,
322- {
323- detect : { cliCommands : [ 'opencode' ] , overridePlatform : 'darwin' } ,
324- expectedConfigPath : '.config/opencode/opencode.json' ,
325- name : 'OpenCode' ,
326- platform : 'darwin' ,
327- } ,
328- {
329- detect : { cliCommands : [ 'codex' ] } ,
330- expectedConfigPath : '.codex/config.toml' ,
331- expectedContentChecks : [ '[mcp_servers.Sanity]' , '[mcp_servers.Sanity.http_headers]' ] ,
332- name : 'Codex CLI' ,
333- } ,
334- {
335- detect : {
336- cliCommands : [ 'codex' ] ,
337- env : {
338- CODEX_HOME :
339- process . platform === 'win32'
340- ? String . raw `C:\tmp\custom-codex-home`
341- : '/tmp/custom-codex-home' ,
342- } ,
343- } ,
344- expectedConfigPath : convertToSystemPath ( '/tmp/custom-codex-home/config.toml' ) ,
345- name : 'Codex CLI' ,
346- } ,
347- {
348- detect : {
349- existsSync : ( p ) => p . includes ( '.config/zed' ) ,
350- overridePlatform : 'darwin' ,
351- } ,
352- expectedConfigPath : '.config/zed/settings.json' ,
353- name : 'Zed' ,
354- platform : 'darwin' ,
355- } ,
356- {
357- detect : {
358- env : { APPDATA : String . raw `C:\Users\test\AppData\Roaming` } ,
359- existsSync : ( p ) => p . includes ( String . raw `AppData\Roaming\Zed` ) ,
360- overridePlatform : 'win32' ,
361- } ,
362- expectedConfigPath : String . raw `AppData\Roaming\Zed\settings.json` ,
363- name : 'Zed' ,
364- platform : 'win32' ,
365- } ,
366- ]
367-
368- // MCPorter has three variants for file format detection
369- const mcporterTestCases : Array < {
370- existsSync : ( p : string ) => boolean
371- expectedConfigPath : string
372- label : string
373- } > = [
374- {
375- existsSync : ( p ) => {
376- const n = p . replaceAll ( '\\' , '/' )
377- if ( n . endsWith ( '/.mcporter' ) ) return true
378- if ( n . endsWith ( '/.mcporter/mcporter.json' ) ) return false
379- if ( n . endsWith ( '/.mcporter/mcporter.jsonc' ) ) return true
380- return false
381- } ,
382- expectedConfigPath : '.mcporter/mcporter.jsonc' ,
383- label : 'existing jsonc config' ,
384- } ,
385- {
386- existsSync : ( p ) => {
387- const n = p . replaceAll ( '\\' , '/' )
388- if ( n . endsWith ( '/.mcporter' ) ) return true
389- if ( n . endsWith ( '/.mcporter/mcporter.json' ) ) return true
390- if ( n . endsWith ( '/.mcporter/mcporter.jsonc' ) ) return false
391- return false
392- } ,
393- expectedConfigPath : '.mcporter/mcporter.json' ,
394- label : 'existing json config' ,
395- } ,
396- {
397- existsSync : ( p ) => {
398- const n = p . replaceAll ( '\\' , '/' )
399- if ( n . endsWith ( '/.mcporter' ) ) return true
400- if ( n . endsWith ( '/.mcporter/mcporter.json' ) ) return false
401- if ( n . endsWith ( '/.mcporter/mcporter.jsonc' ) ) return false
402- return false
403- } ,
404- expectedConfigPath : '.mcporter/mcporter.json' ,
405- label : 'fresh install (defaults to json)' ,
406- } ,
407- ]
408-
40996// ---------------------------------------------------------------------------
41097// Main test suite
41198// ---------------------------------------------------------------------------
@@ -434,30 +121,6 @@ describe.sequential('#mcp:configure', () => {
434121 expect ( pending , 'pending mocks' ) . toEqual ( [ ] )
435122 } )
436123
437- // -------------------------------------------------------------------------
438- // Per-editor detection (table-driven)
439- // -------------------------------------------------------------------------
440-
441- describe ( 'editor detection and configuration' , ( ) => {
442- for ( const tc of editorTestCases ) {
443- const suffix = tc . platform ? ` on ${ tc . platform } ` : ''
444- const envNote = tc . detect . env ? ` (${ Object . keys ( tc . detect . env ) . join ( ', ' ) } )` : ''
445- const label = `detects ${ tc . name } ${ suffix } ${ envNote } and configures it`
446-
447- test . runIf ( ! tc . platform || process . platform === tc . platform ) ( label , ( ) => runEditorTest ( tc ) )
448- }
449-
450- // MCPorter file-format variants
451- for ( const mc of mcporterTestCases ) {
452- test ( `detects MCPorter with ${ mc . label } and configures it` , ( ) =>
453- runEditorTest ( {
454- detect : { existsSync : mc . existsSync } ,
455- expectedConfigPath : mc . expectedConfigPath ,
456- name : 'MCPorter' ,
457- } ) )
458- }
459- } )
460-
461124 // -------------------------------------------------------------------------
462125 // Codex CLI: unparseable TOML skips editor
463126 // -------------------------------------------------------------------------
0 commit comments