@@ -79,4 +79,138 @@ describe("exec approvals gateway methods", () => {
7979 } ) ,
8080 ) ;
8181 } ) ;
82+
83+ it . each ( [
84+ {
85+ method : "exec.approvals.node.get" as const ,
86+ command : "system.execApprovals.get" ,
87+ params : { nodeId : "node-1" } ,
88+ commands : [ ] ,
89+ config : { } ,
90+ } ,
91+ {
92+ method : "exec.approvals.node.set" as const ,
93+ command : "system.execApprovals.set" ,
94+ params : {
95+ nodeId : "node-1" ,
96+ file : { version : 1 , agents : { } } ,
97+ baseHash : "base-hash" ,
98+ } ,
99+ commands : [ "system.execApprovals.set" ] ,
100+ config : { gateway : { nodes : { denyCommands : [ "system.execApprovals.set" ] } } } ,
101+ } ,
102+ ] ) ( "blocks $method outside the effective command policy" , async ( testCase ) => {
103+ const invoke = vi . fn ( ) ;
104+ const respond = vi . fn ( ) ;
105+
106+ await execApprovalsHandlers [ testCase . method ] ( {
107+ req : {
108+ type : "req" ,
109+ id : "req-node-blocked" ,
110+ method : testCase . method ,
111+ params : testCase . params ,
112+ } ,
113+ params : testCase . params ,
114+ client : null ,
115+ isWebchatConnect : ( ) => false ,
116+ respond,
117+ context : {
118+ getRuntimeConfig : ( ) => testCase . config ,
119+ nodeRegistry : {
120+ get : ( ) => ( {
121+ nodeId : "node-1" ,
122+ connId : "conn-1" ,
123+ platform : "windows" ,
124+ deviceFamily : "Windows" ,
125+ declaredCommands : [ testCase . command ] ,
126+ commands : testCase . commands ,
127+ } ) ,
128+ invoke,
129+ } ,
130+ } as never ,
131+ } ) ;
132+
133+ expect ( invoke ) . not . toHaveBeenCalled ( ) ;
134+ expect ( respond ) . toHaveBeenCalledWith (
135+ false ,
136+ undefined ,
137+ expect . objectContaining ( {
138+ code : "INVALID_REQUEST" ,
139+ details : expect . objectContaining ( { command : testCase . command } ) ,
140+ } ) ,
141+ ) ;
142+ } ) ;
143+
144+ it ( "relays approved exec-approval commands" , async ( ) => {
145+ const command = "system.execApprovals.get" ;
146+ const invoke = vi . fn ( ) . mockResolvedValue ( { ok : true , payload : { exists : true } } ) ;
147+ const respond = vi . fn ( ) ;
148+
149+ await execApprovalsHandlers [ "exec.approvals.node.get" ] ( {
150+ req : {
151+ type : "req" ,
152+ id : "req-node-allowed" ,
153+ method : "exec.approvals.node.get" ,
154+ params : { nodeId : "node-1" } ,
155+ } ,
156+ params : { nodeId : "node-1" } ,
157+ client : null ,
158+ isWebchatConnect : ( ) => false ,
159+ respond,
160+ context : {
161+ getRuntimeConfig : ( ) => ( { } ) ,
162+ nodeRegistry : {
163+ get : ( ) => ( {
164+ nodeId : "node-1" ,
165+ connId : "conn-1" ,
166+ platform : "windows" ,
167+ deviceFamily : "Windows" ,
168+ declaredCommands : [ command ] ,
169+ commands : [ command ] ,
170+ } ) ,
171+ invoke,
172+ } ,
173+ } as never ,
174+ } ) ;
175+
176+ expect ( invoke ) . toHaveBeenCalledWith ( { nodeId : "node-1" , command, params : { } } ) ;
177+ expect ( respond ) . toHaveBeenCalledWith ( true , { exists : true } , undefined ) ;
178+ } ) ;
179+
180+ it ( "preserves unavailable details for unknown nodes" , async ( ) => {
181+ const invoke = vi . fn ( ) . mockResolvedValue ( {
182+ ok : false ,
183+ error : { code : "NOT_CONNECTED" , message : "node not connected" } ,
184+ } ) ;
185+ const respond = vi . fn ( ) ;
186+
187+ await execApprovalsHandlers [ "exec.approvals.node.get" ] ( {
188+ req : {
189+ type : "req" ,
190+ id : "req-node-missing" ,
191+ method : "exec.approvals.node.get" ,
192+ params : { nodeId : "missing-node" } ,
193+ } ,
194+ params : { nodeId : "missing-node" } ,
195+ client : null ,
196+ isWebchatConnect : ( ) => false ,
197+ respond,
198+ context : {
199+ getRuntimeConfig : ( ) => ( { } ) ,
200+ nodeRegistry : { get : ( ) => undefined , invoke } ,
201+ } as never ,
202+ } ) ;
203+
204+ expect ( invoke ) . toHaveBeenCalled ( ) ;
205+ expect ( respond ) . toHaveBeenCalledWith (
206+ false ,
207+ undefined ,
208+ expect . objectContaining ( {
209+ code : "UNAVAILABLE" ,
210+ details : {
211+ nodeError : { code : "NOT_CONNECTED" , message : "node not connected" } ,
212+ } ,
213+ } ) ,
214+ ) ;
215+ } ) ;
82216} ) ;
0 commit comments