@@ -631,6 +631,161 @@ test('deferred materialization re-prepares runtime when explicit Metro overrides
631631 fs . rmSync ( tempRoot , { recursive : true , force : true } ) ;
632632} ) ;
633633
634+ test ( 'cdp remote materialization prepares Metro runtime for bridge target discovery' , async ( ) => {
635+ const tempRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-agent-cdp-runtime-' ) ) ;
636+ const stateDir = path . join ( tempRoot , '.state' ) ;
637+ const remoteConfigPath = path . join ( tempRoot , 'remote.json' ) ;
638+ fs . writeFileSync ( remoteConfigPath , JSON . stringify ( { daemonBaseUrl : 'https://daemon.example' } ) ) ;
639+ let prepareRequest : Parameters < AgentDeviceClient [ 'metro' ] [ 'prepare' ] > [ 0 ] | undefined ;
640+
641+ const materialized = await materializeRemoteConnectionForCommand ( {
642+ command : 'cdp' ,
643+ positionals : [ 'target' , 'list' ] ,
644+ flags : {
645+ json : true ,
646+ help : false ,
647+ version : false ,
648+ stateDir,
649+ remoteConfig : remoteConfigPath ,
650+ daemonBaseUrl : 'https://daemon.example' ,
651+ tenant : 'acme' ,
652+ runId : 'run-123' ,
653+ session : 'adc-android' ,
654+ platform : 'android' ,
655+ leaseBackend : 'android-instance' ,
656+ metroProjectRoot : '/tmp/project' ,
657+ metroProxyBaseUrl : 'https://proxy.example.test' ,
658+ metroPublicBaseUrl : 'https://sandbox.example.test' ,
659+ } ,
660+ client : createTestClient ( {
661+ prepare : async ( options ) => {
662+ prepareRequest = options ;
663+ return {
664+ projectRoot : '/tmp/project' ,
665+ kind : 'react-native' ,
666+ dependenciesInstalled : false ,
667+ packageManager : null ,
668+ started : false ,
669+ reused : true ,
670+ pid : 0 ,
671+ logPath : '/tmp/project/.agent-device/metro.log' ,
672+ statusUrl : 'http://127.0.0.1:8081/status' ,
673+ runtimeFilePath : null ,
674+ iosRuntime : { platform : 'ios' } ,
675+ androidRuntime : {
676+ platform : 'android' ,
677+ bundleUrl :
678+ 'https://proxy.example.test/api/metro/runtimes/runtime-1/index.bundle?platform=android' ,
679+ } ,
680+ bridge : null ,
681+ } ;
682+ } ,
683+ } ) ,
684+ } ) ;
685+
686+ assert . equal ( prepareRequest ?. proxyBaseUrl , 'https://proxy.example.test' ) ;
687+ assert . deepEqual ( prepareRequest ?. bridgeScope , {
688+ tenantId : 'acme' ,
689+ runId : 'run-123' ,
690+ leaseId : 'lease-1' ,
691+ } ) ;
692+ assert . deepEqual ( materialized . runtime , {
693+ platform : 'android' ,
694+ bundleUrl :
695+ 'https://proxy.example.test/api/metro/runtimes/runtime-1/index.bundle?platform=android' ,
696+ } ) ;
697+ assert . deepEqual ( readRemoteConnectionState ( { stateDir, session : 'adc-android' } ) ?. metro , {
698+ projectRoot : '/tmp/project' ,
699+ profileKey : remoteConfigPath ,
700+ consumerKey : 'adc-android' ,
701+ } ) ;
702+
703+ fs . rmSync ( tempRoot , { recursive : true , force : true } ) ;
704+ } ) ;
705+
706+ test ( 'cdp remote materialization skips Metro runtime for non-target commands' , async ( ) => {
707+ const tempRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-agent-cdp-memory-' ) ) ;
708+ const stateDir = path . join ( tempRoot , '.state' ) ;
709+ const remoteConfigPath = path . join ( tempRoot , 'remote.json' ) ;
710+ fs . writeFileSync ( path . join ( tempRoot , 'remote.json' ) , JSON . stringify ( { } ) ) ;
711+ let prepared = false ;
712+
713+ try {
714+ const materialized = await materializeRemoteConnectionForCommand ( {
715+ command : 'cdp' ,
716+ positionals : [ 'memory' , 'usage' , 'sample' ] ,
717+ flags : {
718+ json : true ,
719+ help : false ,
720+ version : false ,
721+ stateDir,
722+ remoteConfig : remoteConfigPath ,
723+ daemonBaseUrl : 'https://daemon.example' ,
724+ tenant : 'acme' ,
725+ runId : 'run-123' ,
726+ session : 'adc-android' ,
727+ platform : 'android' ,
728+ leaseBackend : 'android-instance' ,
729+ metroProjectRoot : '/tmp/project' ,
730+ metroProxyBaseUrl : 'https://proxy.example.test' ,
731+ metroPublicBaseUrl : 'https://sandbox.example.test' ,
732+ } ,
733+ client : createTestClient ( {
734+ prepare : async ( ) => {
735+ prepared = true ;
736+ throw new Error ( 'prepare should not be called' ) ;
737+ } ,
738+ } ) ,
739+ } ) ;
740+
741+ assert . equal ( prepared , false ) ;
742+ assert . equal ( materialized . runtime , undefined ) ;
743+ } finally {
744+ fs . rmSync ( tempRoot , { recursive : true , force : true } ) ;
745+ }
746+ } ) ;
747+
748+ test ( 'cdp remote materialization skips Metro runtime without public CDP url' , async ( ) => {
749+ const tempRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-agent-cdp-no-public-' ) ) ;
750+ const stateDir = path . join ( tempRoot , '.state' ) ;
751+ const remoteConfigPath = path . join ( tempRoot , 'remote.json' ) ;
752+ fs . writeFileSync ( remoteConfigPath , JSON . stringify ( { } ) ) ;
753+ let prepared = false ;
754+
755+ try {
756+ const materialized = await materializeRemoteConnectionForCommand ( {
757+ command : 'cdp' ,
758+ positionals : [ 'target' , 'list' ] ,
759+ flags : {
760+ json : true ,
761+ help : false ,
762+ version : false ,
763+ stateDir,
764+ remoteConfig : remoteConfigPath ,
765+ daemonBaseUrl : 'https://daemon.example' ,
766+ tenant : 'acme' ,
767+ runId : 'run-123' ,
768+ session : 'adc-android' ,
769+ platform : 'android' ,
770+ leaseBackend : 'android-instance' ,
771+ metroProjectRoot : '/tmp/project' ,
772+ metroProxyBaseUrl : 'https://proxy.example.test' ,
773+ } ,
774+ client : createTestClient ( {
775+ prepare : async ( ) => {
776+ prepared = true ;
777+ throw new Error ( 'prepare should not be called' ) ;
778+ } ,
779+ } ) ,
780+ } ) ;
781+
782+ assert . equal ( prepared , false ) ;
783+ assert . equal ( materialized . runtime , undefined ) ;
784+ } finally {
785+ fs . rmSync ( tempRoot , { recursive : true , force : true } ) ;
786+ }
787+ } ) ;
788+
634789test ( 'deferred materialization heartbeats an existing lease before dispatch' , async ( ) => {
635790 const tempRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-connect-heartbeat-' ) ) ;
636791 const stateDir = path . join ( tempRoot , '.state' ) ;
0 commit comments