@@ -117,6 +117,128 @@ describe('buildKibanaRequestParams', () => {
117117 } )
118118} )
119119
120+ describe ( 'buildKibanaRequestParams empty-body normalisation (CLI-1)' , ( ) => {
121+ it ( 'sends {} for a POST with optional body fields when none are provided' , ( ) => {
122+ const def : KbApiDefinition = {
123+ name : 'post-thing' ,
124+ namespace : 'widgets' ,
125+ description : 'Post a thing' ,
126+ method : 'POST' ,
127+ path : '/api/widgets' ,
128+ input : {
129+ type : 'object' ,
130+ properties : {
131+ note : { type : 'string' , 'x-found-in' : 'body' } ,
132+ } ,
133+ } ,
134+ }
135+ const result = buildKibanaRequestParams ( def , parsed ( ) )
136+ assert . deepEqual ( result . body , { } , 'POST with no body args must send {} not null/undefined' )
137+ } )
138+
139+ it ( 'sends {} for a DELETE with optional body fields when none are provided' , ( ) => {
140+ const def : KbApiDefinition = {
141+ name : 'delete-thing' ,
142+ namespace : 'widgets' ,
143+ description : 'Delete things' ,
144+ method : 'DELETE' ,
145+ path : '/api/widgets' ,
146+ input : {
147+ type : 'object' ,
148+ properties : {
149+ excludedIds : { type : 'array' , items : { type : 'string' } , 'x-found-in' : 'body' } ,
150+ } ,
151+ } ,
152+ }
153+ const result = buildKibanaRequestParams ( def , parsed ( ) )
154+ assert . deepEqual ( result . body , { } , 'DELETE with optional body must send {} not omit' )
155+ } )
156+
157+ it ( 'sends {} for a PATCH with optional body fields when none are provided' , ( ) => {
158+ const def : KbApiDefinition = {
159+ name : 'patch-thing' ,
160+ namespace : 'widgets' ,
161+ description : 'Patch a thing' ,
162+ method : 'PATCH' ,
163+ path : '/api/widgets/1' ,
164+ input : {
165+ type : 'object' ,
166+ properties : {
167+ note : { type : 'string' , 'x-found-in' : 'body' } ,
168+ } ,
169+ } ,
170+ }
171+ const result = buildKibanaRequestParams ( def , parsed ( ) )
172+ assert . deepEqual ( result . body , { } , 'PATCH with no body args must send {}' )
173+ } )
174+
175+ it ( 'does not set body for a GET with body-routed properties' , ( ) => {
176+ // GET requests must never carry a body regardless of property routing
177+ const def : KbApiDefinition = {
178+ name : 'get-thing' ,
179+ namespace : 'widgets' ,
180+ description : 'Get a thing' ,
181+ method : 'GET' ,
182+ path : '/api/widgets' ,
183+ input : {
184+ type : 'object' ,
185+ properties : {
186+ note : { type : 'string' , 'x-found-in' : 'body' } ,
187+ } ,
188+ } ,
189+ }
190+ const result = buildKibanaRequestParams ( def , parsed ( ) )
191+ assert . equal ( result . body , undefined , 'GET must not send a body' )
192+ } )
193+
194+ it ( 'does not set body for a multipart endpoint with no body fields provided' , ( ) => {
195+ const def : KbApiDefinition = {
196+ name : 'post-saved-objects-import' ,
197+ namespace : 'saved-objects' ,
198+ description : 'Import saved objects' ,
199+ method : 'POST' ,
200+ path : '/api/saved_objects/_import' ,
201+ input : {
202+ type : 'object' ,
203+ properties : {
204+ file : { type : 'string' , 'x-found-in' : 'body' } ,
205+ } ,
206+ } ,
207+ }
208+ // With no file provided, multipart endpoints should have neither body nor multipartFields
209+ const result = buildKibanaRequestParams ( def , parsed ( ) )
210+ assert . equal ( result . body , undefined , 'empty multipart endpoint must not send JSON body' )
211+ assert . equal ( result . multipartFields , undefined , 'empty multipart endpoint must not send empty form' )
212+ } )
213+
214+ it ( 'sends {} for a POST with x-body-root field and no input, using a real definition' , async ( ) => {
215+ const { loadAllKbApis } = await import ( '../../src/kb/apis.ts' )
216+ const apis = await loadAllKbApis ( )
217+ const def = apis . find ( ( d ) => d . namespace === 'misc' && d . name === 'post-security-role-query' )
218+ assert . ok ( def != null , 'expected misc post-security-role-query in manifest' )
219+ const result = buildKibanaRequestParams ( def , parsed ( ) )
220+ assert . deepEqual ( result . body , { } , 'bodyless misc POST must send {} not null/undefined' )
221+ } )
222+
223+ it ( 'sends {} for search-alerts with no args, using a real definition' , async ( ) => {
224+ const { loadAllKbApis } = await import ( '../../src/kb/apis.ts' )
225+ const apis = await loadAllKbApis ( )
226+ const def = apis . find ( ( d ) => d . namespace === 'security-detections-api' && d . name === 'search-alerts' )
227+ assert . ok ( def != null , 'expected security-detections-api search-alerts in manifest' )
228+ const result = buildKibanaRequestParams ( def , parsed ( ) )
229+ assert . deepEqual ( result . body , { } , 'search-alerts with no args must send {} not null/undefined' )
230+ } )
231+
232+ it ( 'sends {} for delete-all-conversations (DELETE with optional body) using a real definition' , async ( ) => {
233+ const { loadAllKbApis } = await import ( '../../src/kb/apis.ts' )
234+ const apis = await loadAllKbApis ( )
235+ const def = apis . find ( ( d ) => d . namespace === 'security-ai-assistant-api' && d . name === 'delete-all-conversations' )
236+ assert . ok ( def != null , 'expected security-ai-assistant-api delete-all-conversations in manifest' )
237+ const result = buildKibanaRequestParams ( def , parsed ( ) )
238+ assert . deepEqual ( result . body , { } , 'DELETE with optional body must send {} not null/undefined' )
239+ } )
240+ } )
241+
120242describe ( 'buildKibanaRequestParams path param requiredness (BUG A regression)' , ( ) => {
121243 // ponytail: no real Kibana definition currently has an optional path param
122244 // (0 of 555 upstream definitions exercise this — see test/kb/register.test.ts),
0 commit comments