@@ -23,10 +23,7 @@ function createTool(overrides: Partial<ToolDefinition> = {}): ToolDefinition {
2323 scheme : z . string ( ) . optional ( ) ,
2424 } ,
2525 stateful : false ,
26- handler : vi . fn ( async ( ) => ( {
27- content : [ createTextContent ( 'ok' ) ] ,
28- isError : false ,
29- } ) ) ,
26+ handler : vi . fn ( async ( ) => { } ) as ToolDefinition [ 'handler' ] ,
3027 ...overrides ,
3128 } ;
3229}
@@ -97,10 +94,9 @@ describe('registerToolCommands', () => {
9794 } ) ;
9895
9996 it ( 'hydrates required args from the active defaults profile' , async ( ) => {
100- const invokeDirect = vi . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' ) . mockResolvedValue ( {
101- content : [ createTextContent ( 'ok' ) ] ,
102- isError : false ,
103- } ) ;
97+ const invokeDirect = vi
98+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
99+ . mockResolvedValue ( undefined ) ;
104100 const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
105101
106102 const tool = createTool ( ) ;
@@ -126,10 +122,9 @@ describe('registerToolCommands', () => {
126122 it ( 'hydrates required args from the explicit --profile override' , async ( ) => {
127123 process . argv = [ 'node' , 'xcodebuildmcp' , 'simulator' , 'run-tool' , '--profile' , 'qa' ] ;
128124
129- const invokeDirect = vi . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' ) . mockResolvedValue ( {
130- content : [ createTextContent ( 'ok' ) ] ,
131- isError : false ,
132- } ) ;
125+ const invokeDirect = vi
126+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
127+ . mockResolvedValue ( undefined ) ;
133128 const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
134129
135130 const tool = createTool ( ) ;
@@ -159,6 +154,8 @@ describe('registerToolCommands', () => {
159154 } ) ;
160155
161156 it ( 'keeps the normal missing-argument error when no hydrated default exists' , async ( ) => {
157+ const consoleError = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
158+
162159 const tool = createTool ( ) ;
163160 const app = createApp ( createCatalog ( [ tool ] ) , {
164161 ...baseRuntimeConfig ,
@@ -167,22 +164,16 @@ describe('registerToolCommands', () => {
167164 activeSessionDefaultsProfile : undefined ,
168165 } ) ;
169166
170- let error : Error | undefined ;
171- try {
172- await app . parseAsync ( [ 'simulator' , 'run-tool' ] ) ;
173- } catch ( thrown ) {
174- error = thrown as Error ;
175- }
167+ await expect ( app . parseAsync ( [ 'simulator' , 'run-tool' ] ) ) . resolves . toBeDefined ( ) ;
176168
177- expect ( error ?. message ) . toContain ( 'Missing required argument: workspace-path' ) ;
178- expect ( error ?. message ) . not . toMatch ( / s e s s i o n d e f a u l t s / i ) ;
169+ expect ( consoleError ) . toHaveBeenCalledWith ( 'Missing required argument: workspace-path' ) ;
170+ expect ( process . exitCode ) . toBe ( 1 ) ;
179171 } ) ;
180172
181173 it ( 'hydrates args before daemon-routed invocation' , async ( ) => {
182- const invokeDirect = vi . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' ) . mockResolvedValue ( {
183- content : [ createTextContent ( 'ok' ) ] ,
184- isError : false ,
185- } ) ;
174+ const invokeDirect = vi
175+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
176+ . mockResolvedValue ( undefined ) ;
186177 const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
187178
188179 const tool = createTool ( { stateful : true } ) ;
@@ -202,10 +193,9 @@ describe('registerToolCommands', () => {
202193 } ) ;
203194
204195 it ( 'lets explicit args override conflicting defaults before invocation' , async ( ) => {
205- const invokeDirect = vi . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' ) . mockResolvedValue ( {
206- content : [ createTextContent ( 'ok' ) ] ,
207- isError : false ,
208- } ) ;
196+ const invokeDirect = vi
197+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
198+ . mockResolvedValue ( undefined ) ;
209199 const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
210200
211201 const tool = createTool ( {
@@ -253,10 +243,9 @@ describe('registerToolCommands', () => {
253243 } ) ;
254244
255245 it ( 'lets --json override configured defaults' , async ( ) => {
256- const invokeDirect = vi . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' ) . mockResolvedValue ( {
257- content : [ createTextContent ( 'ok' ) ] ,
258- isError : false ,
259- } ) ;
246+ const invokeDirect = vi
247+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
248+ . mockResolvedValue ( undefined ) ;
260249 const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
261250
262251 const tool = createTool ( ) ;
@@ -281,4 +270,84 @@ describe('registerToolCommands', () => {
281270
282271 stdoutWrite . mockRestore ( ) ;
283272 } ) ;
273+
274+ it ( 'allows --json to satisfy required arguments' , async ( ) => {
275+ const invokeDirect = vi
276+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
277+ . mockResolvedValue ( undefined ) ;
278+ const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
279+
280+ const tool = createTool ( ) ;
281+ const app = createApp ( createCatalog ( [ tool ] ) , {
282+ ...baseRuntimeConfig ,
283+ sessionDefaults : undefined ,
284+ sessionDefaultsProfiles : undefined ,
285+ activeSessionDefaultsProfile : undefined ,
286+ } ) ;
287+
288+ await expect (
289+ app . parseAsync ( [
290+ 'simulator' ,
291+ 'run-tool' ,
292+ '--json' ,
293+ JSON . stringify ( { workspacePath : 'FromJson.xcworkspace' } ) ,
294+ ] ) ,
295+ ) . resolves . toBeDefined ( ) ;
296+
297+ expect ( invokeDirect ) . toHaveBeenCalledWith (
298+ tool ,
299+ {
300+ workspacePath : 'FromJson.xcworkspace' ,
301+ } ,
302+ expect . any ( Object ) ,
303+ ) ;
304+
305+ stdoutWrite . mockRestore ( ) ;
306+ } ) ;
307+
308+ it ( 'allows array args that begin with a dash' , async ( ) => {
309+ const invokeDirect = vi
310+ . spyOn ( DefaultToolInvoker . prototype , 'invokeDirect' )
311+ . mockResolvedValue ( undefined ) ;
312+ const stdoutWrite = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
313+
314+ const tool = createTool ( {
315+ cliSchema : {
316+ workspacePath : z . string ( ) . describe ( 'Workspace path' ) ,
317+ extraArgs : z . array ( z . string ( ) ) . optional ( ) . describe ( 'Extra args' ) ,
318+ } ,
319+ mcpSchema : {
320+ workspacePath : z . string ( ) . describe ( 'Workspace path' ) ,
321+ extraArgs : z . array ( z . string ( ) ) . optional ( ) . describe ( 'Extra args' ) ,
322+ } ,
323+ } ) ;
324+ const app = createApp ( createCatalog ( [ tool ] ) , {
325+ ...baseRuntimeConfig ,
326+ sessionDefaults : undefined ,
327+ sessionDefaultsProfiles : undefined ,
328+ activeSessionDefaultsProfile : undefined ,
329+ } ) ;
330+
331+ await expect (
332+ app . parseAsync ( [
333+ 'simulator' ,
334+ 'run-tool' ,
335+ '--workspace-path' ,
336+ 'App.xcworkspace' ,
337+ '--extra-args' ,
338+ '-only-testing:AppTests' ,
339+ ] ) ,
340+ ) . resolves . toBeDefined ( ) ;
341+
342+ expect ( invokeDirect ) . toHaveBeenCalledWith (
343+ tool ,
344+ {
345+ workspacePath : 'App.xcworkspace' ,
346+ extraArgs : [ '-only-testing:AppTests' ] ,
347+ } ,
348+ expect . any ( Object ) ,
349+ ) ;
350+
351+ stdoutWrite . mockRestore ( ) ;
352+ } ) ;
284353} ) ;
0 commit comments