@@ -188,6 +188,204 @@ func TestRequestParseStep_WildcardPathParam_SingleSegment(t *testing.T) {
188188 }
189189}
190190
191+ func TestRequestParseStep_ParseBody_FormURLEncoded (t * testing.T ) {
192+ factory := NewRequestParseStepFactory ()
193+ step , err := factory ("parse-form" , map [string ]any {
194+ "parse_body" : true ,
195+ }, nil )
196+ if err != nil {
197+ t .Fatalf ("factory error: %v" , err )
198+ }
199+
200+ body := bytes .NewBufferString (`Body=Hello&From=%2B15551234567&To=%2B15559876543&MessageSid=SM1234` )
201+ req , _ := http .NewRequest ("POST" , "/webhook" , body )
202+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
203+
204+ pc := NewPipelineContext (nil , map [string ]any {
205+ "_http_request" : req ,
206+ })
207+
208+ result , err := step .Execute (context .Background (), pc )
209+ if err != nil {
210+ t .Fatalf ("execute error: %v" , err )
211+ }
212+
213+ bodyData , ok := result .Output ["body" ].(map [string ]any )
214+ if ! ok {
215+ t .Fatal ("expected body in output" )
216+ }
217+ if bodyData ["Body" ] != "Hello" {
218+ t .Errorf ("expected Body='Hello', got %v" , bodyData ["Body" ])
219+ }
220+ if bodyData ["From" ] != "+15551234567" {
221+ t .Errorf ("expected From='+15551234567', got %v" , bodyData ["From" ])
222+ }
223+ if bodyData ["To" ] != "+15559876543" {
224+ t .Errorf ("expected To='+15559876543', got %v" , bodyData ["To" ])
225+ }
226+ if bodyData ["MessageSid" ] != "SM1234" {
227+ t .Errorf ("expected MessageSid='SM1234', got %v" , bodyData ["MessageSid" ])
228+ }
229+
230+ // Raw body should be cached in metadata
231+ rawBody , ok := pc .Metadata ["_raw_body" ].([]byte )
232+ if ! ok {
233+ t .Fatal ("expected _raw_body in metadata" )
234+ }
235+ if string (rawBody ) != `Body=Hello&From=%2B15551234567&To=%2B15559876543&MessageSid=SM1234` {
236+ t .Errorf ("unexpected _raw_body: %s" , rawBody )
237+ }
238+ }
239+
240+ func TestRequestParseStep_ParseBody_FormURLEncoded_MultiValue (t * testing.T ) {
241+ factory := NewRequestParseStepFactory ()
242+ step , err := factory ("parse-form-multi" , map [string ]any {
243+ "parse_body" : true ,
244+ }, nil )
245+ if err != nil {
246+ t .Fatalf ("factory error: %v" , err )
247+ }
248+
249+ body := bytes .NewBufferString (`tag=foo&tag=bar&name=test` )
250+ req , _ := http .NewRequest ("POST" , "/webhook" , body )
251+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
252+
253+ pc := NewPipelineContext (nil , map [string ]any {
254+ "_http_request" : req ,
255+ })
256+
257+ result , err := step .Execute (context .Background (), pc )
258+ if err != nil {
259+ t .Fatalf ("execute error: %v" , err )
260+ }
261+
262+ bodyData , ok := result .Output ["body" ].(map [string ]any )
263+ if ! ok {
264+ t .Fatal ("expected body in output" )
265+ }
266+ // Single value should be a string
267+ if bodyData ["name" ] != "test" {
268+ t .Errorf ("expected name='test', got %v" , bodyData ["name" ])
269+ }
270+ // Multiple values should be []string
271+ tags , ok := bodyData ["tag" ].([]string )
272+ if ! ok {
273+ t .Fatalf ("expected tag to be []string, got %T" , bodyData ["tag" ])
274+ }
275+ if len (tags ) != 2 || tags [0 ] != "foo" || tags [1 ] != "bar" {
276+ t .Errorf ("expected tag=['foo','bar'], got %v" , tags )
277+ }
278+ }
279+
280+ func TestRequestParseStep_ParseBody_FormURLEncoded_ContentTypeWithCharset (t * testing.T ) {
281+ factory := NewRequestParseStepFactory ()
282+ step , err := factory ("parse-form-charset" , map [string ]any {
283+ "parse_body" : true ,
284+ }, nil )
285+ if err != nil {
286+ t .Fatalf ("factory error: %v" , err )
287+ }
288+
289+ body := bytes .NewBufferString (`key=value` )
290+ req , _ := http .NewRequest ("POST" , "/webhook" , body )
291+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded; charset=utf-8" )
292+
293+ pc := NewPipelineContext (nil , map [string ]any {
294+ "_http_request" : req ,
295+ })
296+
297+ result , err := step .Execute (context .Background (), pc )
298+ if err != nil {
299+ t .Fatalf ("execute error: %v" , err )
300+ }
301+
302+ bodyData , ok := result .Output ["body" ].(map [string ]any )
303+ if ! ok {
304+ t .Fatal ("expected body in output" )
305+ }
306+ if bodyData ["key" ] != "value" {
307+ t .Errorf ("expected key='value', got %v" , bodyData ["key" ])
308+ }
309+ }
310+
311+ func TestRequestParseStep_ParseBody_FormURLEncoded_CachedRawBody (t * testing.T ) {
312+ // Simulate scenario where req.Body has already been consumed by a prior step
313+ // (e.g. step.webhook_verify) and the raw bytes are cached in _raw_body.
314+ factory := NewRequestParseStepFactory ()
315+ step , err := factory ("parse-form-cached" , map [string ]any {
316+ "parse_body" : true ,
317+ }, nil )
318+ if err != nil {
319+ t .Fatalf ("factory error: %v" , err )
320+ }
321+
322+ rawBody := `Body=Hello&From=%2B15551234567`
323+ // req.Body is empty/consumed (simulate body already read)
324+ req , _ := http .NewRequest ("POST" , "/webhook" , bytes .NewBufferString ("" ))
325+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
326+
327+ pc := NewPipelineContext (nil , map [string ]any {
328+ "_http_request" : req ,
329+ "_raw_body" : []byte (rawBody ),
330+ })
331+
332+ result , err := step .Execute (context .Background (), pc )
333+ if err != nil {
334+ t .Fatalf ("execute error: %v" , err )
335+ }
336+
337+ bodyData , ok := result .Output ["body" ].(map [string ]any )
338+ if ! ok {
339+ t .Fatal ("expected body in output when reading from _raw_body cache" )
340+ }
341+ if bodyData ["Body" ] != "Hello" {
342+ t .Errorf ("expected Body='Hello', got %v" , bodyData ["Body" ])
343+ }
344+ if bodyData ["From" ] != "+15551234567" {
345+ t .Errorf ("expected From='+15551234567', got %v" , bodyData ["From" ])
346+ }
347+ }
348+
349+ func TestRequestParseStep_ParseBody_JSON_CachesRawBody (t * testing.T ) {
350+ // Verify that reading a JSON body also caches the raw bytes in _raw_body.
351+ factory := NewRequestParseStepFactory ()
352+ step , err := factory ("parse-json-cache" , map [string ]any {
353+ "parse_body" : true ,
354+ }, nil )
355+ if err != nil {
356+ t .Fatalf ("factory error: %v" , err )
357+ }
358+
359+ bodyStr := `{"name":"test"}`
360+ req , _ := http .NewRequest ("POST" , "/api/resource" , bytes .NewBufferString (bodyStr ))
361+ req .Header .Set ("Content-Type" , "application/json" )
362+
363+ pc := NewPipelineContext (nil , map [string ]any {
364+ "_http_request" : req ,
365+ })
366+
367+ result , err := step .Execute (context .Background (), pc )
368+ if err != nil {
369+ t .Fatalf ("execute error: %v" , err )
370+ }
371+
372+ bodyData , ok := result .Output ["body" ].(map [string ]any )
373+ if ! ok {
374+ t .Fatal ("expected body in output" )
375+ }
376+ if bodyData ["name" ] != "test" {
377+ t .Errorf ("expected name='test', got %v" , bodyData ["name" ])
378+ }
379+
380+ rawBody , ok := pc .Metadata ["_raw_body" ].([]byte )
381+ if ! ok {
382+ t .Fatal ("expected _raw_body cached in metadata for JSON body" )
383+ }
384+ if string (rawBody ) != bodyStr {
385+ t .Errorf ("unexpected _raw_body: %s" , rawBody )
386+ }
387+ }
388+
191389func TestRequestParseStep_EmptyConfig (t * testing.T ) {
192390 factory := NewRequestParseStepFactory ()
193391 step , err := factory ("parse-empty" , map [string ]any {}, nil )
0 commit comments