1- /**
2- * Tests for get_device_app_path plugin (unified)
3- * Following CLAUDE.md testing standards with literal validation
4- * Using dependency injection for deterministic testing
5- */
6-
71import { describe , it , expect , beforeEach } from 'vitest' ;
2+ import { DERIVED_DATA_DIR } from '../../../../utils/log-paths.ts' ;
83import * as z from 'zod' ;
94import {
105 createMockCommandResponse ,
116 createMockExecutor ,
127} from '../../../../test-utils/mock-executors.ts' ;
138import { schema , handler , get_device_app_pathLogic } from '../get_device_app_path.ts' ;
149import { sessionStore } from '../../../../utils/session-store.ts' ;
10+ import { createMockToolHandlerContext } from '../../../../test-utils/test-helpers.ts' ;
11+
12+ const runLogic = async ( logic : ( ) => Promise < unknown > ) => {
13+ const { result, run } = createMockToolHandlerContext ( ) ;
14+ const response = await run ( logic ) ;
15+
16+ if (
17+ response &&
18+ typeof response === 'object' &&
19+ 'content' in ( response as Record < string , unknown > )
20+ ) {
21+ return response as {
22+ content : Array < { type : string ; text ?: string ; data ?: string ; mimeType ?: string } > ;
23+ isError ?: boolean ;
24+ nextStepParams ?: unknown ;
25+ } ;
26+ }
27+
28+ const text = result . text ( ) ;
29+ const textContent = text . length > 0 ? [ { type : 'text' as const , text } ] : [ ] ;
30+ const imageContent = result . attachments . map ( ( attachment ) => ( {
31+ type : 'image' as const ,
32+ data : attachment . data ,
33+ mimeType : attachment . mimeType ,
34+ } ) ) ;
35+
36+ return {
37+ content : [ ...textContent , ...imageContent ] ,
38+ isError : result . isError ( ) ? true : undefined ,
39+ nextStepParams : result . nextStepParams ,
40+ attachments : result . attachments ,
41+ text,
42+ } ;
43+ } ;
1544
1645describe ( 'get_device_app_path plugin' , ( ) => {
1746 beforeEach ( ( ) => {
@@ -107,12 +136,14 @@ describe('get_device_app_path plugin', () => {
107136 ) ;
108137 } ;
109138
110- await get_device_app_pathLogic (
111- {
112- projectPath : '/path/to/project.xcodeproj' ,
113- scheme : 'MyScheme' ,
114- } ,
115- mockExecutor ,
139+ await runLogic ( ( ) =>
140+ get_device_app_pathLogic (
141+ {
142+ projectPath : '/path/to/project.xcodeproj' ,
143+ scheme : 'MyScheme' ,
144+ } ,
145+ mockExecutor ,
146+ ) ,
116147 ) ;
117148
118149 expect ( calls ) . toHaveLength ( 1 ) ;
@@ -128,6 +159,8 @@ describe('get_device_app_path plugin', () => {
128159 'Debug' ,
129160 '-destination' ,
130161 'generic/platform=iOS' ,
162+ '-derivedDataPath' ,
163+ DERIVED_DATA_DIR ,
131164 ] ,
132165 logPrefix : 'Get App Path' ,
133166 useShell : false ,
@@ -161,13 +194,15 @@ describe('get_device_app_path plugin', () => {
161194 ) ;
162195 } ;
163196
164- await get_device_app_pathLogic (
165- {
166- projectPath : '/path/to/project.xcodeproj' ,
167- scheme : 'MyScheme' ,
168- platform : 'watchOS' ,
169- } ,
170- mockExecutor ,
197+ await runLogic ( ( ) =>
198+ get_device_app_pathLogic (
199+ {
200+ projectPath : '/path/to/project.xcodeproj' ,
201+ scheme : 'MyScheme' ,
202+ platform : 'watchOS' ,
203+ } ,
204+ mockExecutor ,
205+ ) ,
171206 ) ;
172207
173208 expect ( calls ) . toHaveLength ( 1 ) ;
@@ -183,6 +218,8 @@ describe('get_device_app_path plugin', () => {
183218 'Debug' ,
184219 '-destination' ,
185220 'generic/platform=watchOS' ,
221+ '-derivedDataPath' ,
222+ DERIVED_DATA_DIR ,
186223 ] ,
187224 logPrefix : 'Get App Path' ,
188225 useShell : false ,
@@ -216,12 +253,14 @@ describe('get_device_app_path plugin', () => {
216253 ) ;
217254 } ;
218255
219- await get_device_app_pathLogic (
220- {
221- workspacePath : '/path/to/workspace.xcworkspace' ,
222- scheme : 'MyScheme' ,
223- } ,
224- mockExecutor ,
256+ await runLogic ( ( ) =>
257+ get_device_app_pathLogic (
258+ {
259+ workspacePath : '/path/to/workspace.xcworkspace' ,
260+ scheme : 'MyScheme' ,
261+ } ,
262+ mockExecutor ,
263+ ) ,
225264 ) ;
226265
227266 expect ( calls ) . toHaveLength ( 1 ) ;
@@ -237,6 +276,8 @@ describe('get_device_app_path plugin', () => {
237276 'Debug' ,
238277 '-destination' ,
239278 'generic/platform=iOS' ,
279+ '-derivedDataPath' ,
280+ DERIVED_DATA_DIR ,
240281 ] ,
241282 logPrefix : 'Get App Path' ,
242283 useShell : false ,
@@ -251,29 +292,24 @@ describe('get_device_app_path plugin', () => {
251292 'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n' ,
252293 } ) ;
253294
254- const result = await get_device_app_pathLogic (
255- {
256- projectPath : '/path/to/project.xcodeproj' ,
257- scheme : 'MyScheme' ,
258- } ,
259- mockExecutor ,
260- ) ;
261-
262- expect ( result ) . toEqual ( {
263- content : [
295+ const result = await runLogic ( ( ) =>
296+ get_device_app_pathLogic (
264297 {
265- type : 'text ' ,
266- text : '✅ App path retrieved successfully: /path/to/build/Debug-iphoneos/MyApp.app ' ,
298+ projectPath : '/path/to/project.xcodeproj ' ,
299+ scheme : 'MyScheme ' ,
267300 } ,
268- ] ,
269- nextStepParams : {
270- get_app_bundle_id : { appPath : '/path/to/build/Debug-iphoneos/MyApp.app' } ,
271- install_app_device : {
272- deviceId : 'DEVICE_UDID' ,
273- appPath : '/path/to/build/Debug-iphoneos/MyApp.app' ,
274- } ,
275- launch_app_device : { deviceId : 'DEVICE_UDID' , bundleId : 'BUNDLE_ID' } ,
301+ mockExecutor ,
302+ ) ,
303+ ) ;
304+
305+ expect ( result . isError ) . toBeFalsy ( ) ;
306+ expect ( result . nextStepParams ) . toEqual ( {
307+ get_app_bundle_id : { appPath : '/path/to/build/Debug-iphoneos/MyApp.app' } ,
308+ install_app_device : {
309+ deviceId : 'DEVICE_UDID' ,
310+ appPath : '/path/to/build/Debug-iphoneos/MyApp.app' ,
276311 } ,
312+ launch_app_device : { deviceId : 'DEVICE_UDID' , bundleId : 'BUNDLE_ID' } ,
277313 } ) ;
278314 } ) ;
279315
@@ -283,23 +319,18 @@ describe('get_device_app_path plugin', () => {
283319 error : 'xcodebuild: error: The project does not exist.' ,
284320 } ) ;
285321
286- const result = await get_device_app_pathLogic (
287- {
288- projectPath : '/path/to/nonexistent.xcodeproj' ,
289- scheme : 'MyScheme' ,
290- } ,
291- mockExecutor ,
292- ) ;
293-
294- expect ( result ) . toEqual ( {
295- content : [
322+ const result = await runLogic ( ( ) =>
323+ get_device_app_pathLogic (
296324 {
297- type : 'text ' ,
298- text : 'Failed to get app path: xcodebuild: error: The project does not exist. ' ,
325+ projectPath : '/path/to/nonexistent.xcodeproj ' ,
326+ scheme : 'MyScheme ' ,
299327 } ,
300- ] ,
301- isError : true ,
302- } ) ;
328+ mockExecutor ,
329+ ) ,
330+ ) ;
331+
332+ expect ( result . isError ) . toBe ( true ) ;
333+ expect ( result . nextStepParams ) . toBeUndefined ( ) ;
303334 } ) ;
304335
305336 it ( 'should return exact parse failure response' , async ( ) => {
@@ -308,23 +339,18 @@ describe('get_device_app_path plugin', () => {
308339 output : 'Build settings without required fields' ,
309340 } ) ;
310341
311- const result = await get_device_app_pathLogic (
312- {
313- projectPath : '/path/to/project.xcodeproj' ,
314- scheme : 'MyScheme' ,
315- } ,
316- mockExecutor ,
317- ) ;
318-
319- expect ( result ) . toEqual ( {
320- content : [
342+ const result = await runLogic ( ( ) =>
343+ get_device_app_pathLogic (
321344 {
322- type : 'text ' ,
323- text : 'Failed to extract app path from build settings. Make sure the app has been built first. ' ,
345+ projectPath : '/path/to/project.xcodeproj ' ,
346+ scheme : 'MyScheme ' ,
324347 } ,
325- ] ,
326- isError : true ,
327- } ) ;
348+ mockExecutor ,
349+ ) ,
350+ ) ;
351+
352+ expect ( result . isError ) . toBe ( true ) ;
353+ expect ( result . nextStepParams ) . toBeUndefined ( ) ;
328354 } ) ;
329355
330356 it ( 'should include optional configuration parameter in command' , async ( ) => {
@@ -353,13 +379,15 @@ describe('get_device_app_path plugin', () => {
353379 ) ;
354380 } ;
355381
356- await get_device_app_pathLogic (
357- {
358- projectPath : '/path/to/project.xcodeproj' ,
359- scheme : 'MyScheme' ,
360- configuration : 'Release' ,
361- } ,
362- mockExecutor ,
382+ await runLogic ( ( ) =>
383+ get_device_app_pathLogic (
384+ {
385+ projectPath : '/path/to/project.xcodeproj' ,
386+ scheme : 'MyScheme' ,
387+ configuration : 'Release' ,
388+ } ,
389+ mockExecutor ,
390+ ) ,
363391 ) ;
364392
365393 expect ( calls ) . toHaveLength ( 1 ) ;
@@ -375,6 +403,8 @@ describe('get_device_app_path plugin', () => {
375403 'Release' ,
376404 '-destination' ,
377405 'generic/platform=iOS' ,
406+ '-derivedDataPath' ,
407+ DERIVED_DATA_DIR ,
378408 ] ,
379409 logPrefix : 'Get App Path' ,
380410 useShell : false ,
@@ -393,47 +423,18 @@ describe('get_device_app_path plugin', () => {
393423 return Promise . reject ( new Error ( 'Network error' ) ) ;
394424 } ;
395425
396- const result = await get_device_app_pathLogic (
397- {
398- projectPath : '/path/to/project.xcodeproj' ,
399- scheme : 'MyScheme' ,
400- } ,
401- mockExecutor ,
402- ) ;
403-
404- expect ( result ) . toEqual ( {
405- content : [
426+ const result = await runLogic ( ( ) =>
427+ get_device_app_pathLogic (
406428 {
407- type : 'text ' ,
408- text : 'Error retrieving app path: Network error ' ,
429+ projectPath : '/path/to/project.xcodeproj ' ,
430+ scheme : 'MyScheme ' ,
409431 } ,
410- ] ,
411- isError : true ,
412- } ) ;
413- } ) ;
414-
415- it ( 'should return exact string error handling response' , async ( ) => {
416- const mockExecutor = ( ) => {
417- return Promise . reject ( 'String error' ) ;
418- } ;
419-
420- const result = await get_device_app_pathLogic (
421- {
422- projectPath : '/path/to/project.xcodeproj' ,
423- scheme : 'MyScheme' ,
424- } ,
425- mockExecutor ,
432+ mockExecutor ,
433+ ) ,
426434 ) ;
427435
428- expect ( result ) . toEqual ( {
429- content : [
430- {
431- type : 'text' ,
432- text : 'Error retrieving app path: String error' ,
433- } ,
434- ] ,
435- isError : true ,
436- } ) ;
436+ expect ( result . isError ) . toBe ( true ) ;
437+ expect ( result . nextStepParams ) . toBeUndefined ( ) ;
437438 } ) ;
438439 } ) ;
439440} ) ;
0 commit comments