@@ -8,8 +8,11 @@ installBaseProgramMocks();
88let registerNodesCli : typeof import ( "./nodes-cli.js" ) . registerNodesCli ;
99
1010type GatewayCallRequest = {
11+ clientName ?: string ;
1112 method ?: string ;
13+ mode ?: string ;
1214 params ?: unknown ;
15+ scopes ?: unknown ;
1316} ;
1417
1518function formatRuntimeLogCallArg ( value : unknown ) : string {
@@ -434,19 +437,103 @@ describe("cli program (nodes basics)", () => {
434437
435438 expectGatewayRequest ( "node.list" , { } ) ;
436439 expectGatewayRequest ( "node.describe" , { nodeId : "ios-node" } ) ;
440+ const describeRequest = gatewayRequests ( ) . find (
441+ ( candidate ) => candidate . method === "node.describe" ,
442+ ) ;
443+ expect ( describeRequest ?. clientName ) . toBe ( "cli" ) ;
444+ expect ( describeRequest ?. mode ) . toBe ( "cli" ) ;
437445
438446 const out = getRuntimeOutput ( ) ;
439447 expect ( out ) . toContain ( "Commands" ) ;
440448 expect ( out ) . toContain ( "canvas.eval" ) ;
441449 } ) ;
442450
443- it ( "runs nodes approve and calls node.pair.approve" , async ( ) => {
444- callGateway . mockResolvedValue ( {
445- requestId : "r1" ,
446- node : { nodeId : "n1" , token : "t1" } ,
451+ it ( "runs nodes approve with the pending request approval scopes" , async ( ) => {
452+ callGateway . mockImplementation ( async ( ...args : unknown [ ] ) => {
453+ const opts = ( args [ 0 ] ?? { } ) as { method ?: string } ;
454+ if ( opts . method === "node.pair.list" ) {
455+ return {
456+ pending : [
457+ {
458+ requestId : "r1" ,
459+ nodeId : "n1" ,
460+ ts : Date . now ( ) ,
461+ requiredApproveScopes : [ "operator.pairing" , "operator.admin" ] ,
462+ } ,
463+ ] ,
464+ paired : [ ] ,
465+ } ;
466+ }
467+ if ( opts . method === "node.pair.approve" ) {
468+ return {
469+ requestId : "r1" ,
470+ node : { nodeId : "n1" , token : "t1" } ,
471+ } ;
472+ }
473+ return { ok : true } ;
447474 } ) ;
475+
448476 await runProgram ( [ "nodes" , "approve" , "r1" ] ) ;
477+ expectGatewayRequest ( "node.pair.list" , { } ) ;
449478 expectGatewayRequest ( "node.pair.approve" , { requestId : "r1" } ) ;
479+ const listRequest = gatewayRequests ( ) . find (
480+ ( candidate ) => candidate . method === "node.pair.list" ,
481+ ) ;
482+ const approveRequest = gatewayRequests ( ) . find (
483+ ( candidate ) => candidate . method === "node.pair.approve" ,
484+ ) ;
485+ expect ( listRequest ?. clientName ) . toBe ( "gateway-client" ) ;
486+ expect ( listRequest ?. mode ) . toBe ( "backend" ) ;
487+ expect ( approveRequest ?. scopes ) . toEqual ( [ "operator.pairing" , "operator.admin" ] ) ;
488+ expect ( approveRequest ?. clientName ) . toBe ( "gateway-client" ) ;
489+ expect ( approveRequest ?. mode ) . toBe ( "backend" ) ;
490+ } ) ;
491+
492+ it ( "falls back to command-derived nodes approve scopes" , async ( ) => {
493+ callGateway . mockImplementation ( async ( ...args : unknown [ ] ) => {
494+ const opts = ( args [ 0 ] ?? { } ) as { method ?: string } ;
495+ if ( opts . method === "node.pair.list" ) {
496+ return {
497+ pending : [
498+ {
499+ requestId : "r1" ,
500+ nodeId : "n1" ,
501+ ts : Date . now ( ) ,
502+ commands : [ "system.run" ] ,
503+ } ,
504+ ] ,
505+ paired : [ ] ,
506+ } ;
507+ }
508+ if ( opts . method === "node.pair.approve" ) {
509+ return {
510+ requestId : "r1" ,
511+ node : { nodeId : "n1" , token : "t1" } ,
512+ } ;
513+ }
514+ return { ok : true } ;
515+ } ) ;
516+
517+ await runProgram ( [ "nodes" , "approve" , "r1" ] ) ;
518+
519+ const approveRequest = gatewayRequests ( ) . find (
520+ ( candidate ) => candidate . method === "node.pair.approve" ,
521+ ) ;
522+ expect ( approveRequest ?. scopes ) . toEqual ( [ "operator.pairing" , "operator.admin" ] ) ;
523+ } ) ;
524+
525+ it ( "rejects unsupported node approval backend methods at runtime" , async ( ) => {
526+ const { callNodePairApprovalGatewayCliRuntime } = await import ( "./nodes-cli/rpc.runtime.js" ) ;
527+
528+ await expect (
529+ callNodePairApprovalGatewayCliRuntime (
530+ "node.invoke" as never ,
531+ { json : true } ,
532+ { } ,
533+ { scopes : [ "operator.admin" ] } ,
534+ ) ,
535+ ) . rejects . toThrow ( "unsupported node pair approval gateway method: node.invoke" ) ;
536+ expect ( callGateway ) . not . toHaveBeenCalled ( ) ;
450537 } ) ;
451538
452539 it ( "runs nodes remove and calls node.pair.remove" , async ( ) => {
@@ -500,5 +587,8 @@ describe("cli program (nodes basics)", () => {
500587 timeoutMs : 15000 ,
501588 idempotencyKey : "idem-test" ,
502589 } ) ;
590+ const invokeRequest = gatewayRequests ( ) . find ( ( candidate ) => candidate . method === "node.invoke" ) ;
591+ expect ( invokeRequest ?. clientName ) . toBe ( "cli" ) ;
592+ expect ( invokeRequest ?. mode ) . toBe ( "cli" ) ;
503593 } ) ;
504594} ) ;
0 commit comments