@@ -441,4 +441,135 @@ func TestConnectionUpsertPartialUpdates(t *testing.T) {
441441
442442 t .Logf ("Successfully upserted connection %s with source-name only" , connID )
443443 })
444+
445+ // Regression test for https://github.com/hookdeck/hookdeck-cli/issues/192:
446+ // --rule-filter-headers (and other filter flags) should store JSON as a parsed
447+ // object, not as an escaped string.
448+ t .Run ("FilterHeadersJSONStoredAsObject" , func (t * testing.T ) {
449+ if testing .Short () {
450+ t .Skip ("Skipping acceptance test in short mode" )
451+ }
452+
453+ cli := NewCLIRunner (t )
454+ timestamp := generateTimestamp ()
455+
456+ connName := "test-filter-headers-" + timestamp
457+ sourceName := "test-filter-src-" + timestamp
458+ destName := "test-filter-dst-" + timestamp
459+
460+ // Create a connection using --rule-filter-headers with a JSON object
461+ var createResp map [string ]interface {}
462+ err := cli .RunJSON (& createResp ,
463+ "gateway" , "connection" , "upsert" , connName ,
464+ "--source-name" , sourceName ,
465+ "--source-type" , "WEBHOOK" ,
466+ "--destination-name" , destName ,
467+ "--destination-type" , "HTTP" ,
468+ "--destination-url" , "https://example.com/webhook" ,
469+ "--rule-filter-headers" , `{"x-shopify-topic":{"$startsWith":"order/"}}` ,
470+ )
471+ require .NoError (t , err , "Should create connection with --rule-filter-headers JSON" )
472+
473+ connID , ok := createResp ["id" ].(string )
474+ require .True (t , ok && connID != "" , "Expected connection ID in response" )
475+
476+ t .Cleanup (func () {
477+ deleteConnection (t , cli , connID )
478+ })
479+
480+ // Verify source and destination were created correctly
481+ source , ok := createResp ["source" ].(map [string ]interface {})
482+ require .True (t , ok , "Expected source object in response" )
483+ assert .Equal (t , sourceName , source ["name" ], "Source name should match" )
484+
485+ dest , ok := createResp ["destination" ].(map [string ]interface {})
486+ require .True (t , ok , "Expected destination object in response" )
487+ assert .Equal (t , destName , dest ["name" ], "Destination name should match" )
488+
489+ // Verify the filter rule has headers as a JSON object, not an escaped string
490+ rules , ok := createResp ["rules" ].([]interface {})
491+ require .True (t , ok , "Expected rules array in response" )
492+
493+ foundFilter := false
494+ for _ , r := range rules {
495+ rule , ok := r .(map [string ]interface {})
496+ if ! ok || rule ["type" ] != "filter" {
497+ continue
498+ }
499+ foundFilter = true
500+
501+ headers := rule ["headers" ]
502+ _ , isString := headers .(string )
503+ assert .False (t , isString ,
504+ "--rule-filter-headers should store JSON as an object, not an escaped string; got: %v" , headers )
505+
506+ headersMap , isMap := headers .(map [string ]interface {})
507+ assert .True (t , isMap ,
508+ "headers should be a JSON object (map[string]interface{}), got %T" , headers )
509+ assert .Contains (t , headersMap , "x-shopify-topic" ,
510+ "headers object should contain the expected key" )
511+ break
512+ }
513+ assert .True (t , foundFilter , "Should have a filter rule" )
514+
515+ t .Logf ("Successfully verified --rule-filter-headers stores JSON as object for connection %s" , connID )
516+ })
517+
518+ // Verify that --rule-filter-body JSON is also stored as an object.
519+ t .Run ("FilterBodyJSONStoredAsObject" , func (t * testing.T ) {
520+ if testing .Short () {
521+ t .Skip ("Skipping acceptance test in short mode" )
522+ }
523+
524+ cli := NewCLIRunner (t )
525+ timestamp := generateTimestamp ()
526+
527+ connName := "test-filter-body-" + timestamp
528+ sourceName := "test-filter-body-src-" + timestamp
529+ destName := "test-filter-body-dst-" + timestamp
530+
531+ var createResp map [string ]interface {}
532+ err := cli .RunJSON (& createResp ,
533+ "gateway" , "connection" , "upsert" , connName ,
534+ "--source-name" , sourceName ,
535+ "--source-type" , "WEBHOOK" ,
536+ "--destination-name" , destName ,
537+ "--destination-type" , "HTTP" ,
538+ "--destination-url" , "https://example.com/webhook" ,
539+ "--rule-filter-body" , `{"event_type":"payment"}` ,
540+ )
541+ require .NoError (t , err , "Should create connection with --rule-filter-body JSON" )
542+
543+ connID , ok := createResp ["id" ].(string )
544+ require .True (t , ok && connID != "" , "Expected connection ID in response" )
545+
546+ t .Cleanup (func () {
547+ deleteConnection (t , cli , connID )
548+ })
549+
550+ rules , ok := createResp ["rules" ].([]interface {})
551+ require .True (t , ok , "Expected rules array in response" )
552+
553+ foundFilter := false
554+ for _ , r := range rules {
555+ rule , ok := r .(map [string ]interface {})
556+ if ! ok || rule ["type" ] != "filter" {
557+ continue
558+ }
559+ foundFilter = true
560+
561+ body := rule ["body" ]
562+ _ , isString := body .(string )
563+ assert .False (t , isString ,
564+ "--rule-filter-body should store JSON as an object, not an escaped string; got: %v" , body )
565+
566+ bodyMap , isMap := body .(map [string ]interface {})
567+ assert .True (t , isMap , "body should be a JSON object, got %T" , body )
568+ assert .Contains (t , bodyMap , "event_type" , "body object should contain the expected key" )
569+ break
570+ }
571+ assert .True (t , foundFilter , "Should have a filter rule" )
572+
573+ t .Logf ("Successfully verified --rule-filter-body stores JSON as object for connection %s" , connID )
574+ })
444575}
0 commit comments