11import { test , expect , describe , afterEach } from "bun:test" ;
22import { CliError } from "../../lib/errors.ts" ;
33import { stubFetch , useCaptureLog } from "../../test/lib/stubs.ts" ;
4- import { buildForwardHeaders , forwardDelivery , parseHeaderPairs } from "./forward.ts" ;
4+ import { buildForwardHeaders , forwardDelivery , parseHeaderFlag } from "./forward.ts" ;
55
66const originalFetch = globalThis . fetch ;
77
8- describe ( "parseHeaderPairs" , ( ) => {
9- const parseCases : Array < {
10- label : string ;
11- value : string | undefined ;
12- expected : Record < string , string > ;
13- } > = [
14- { label : "undefined" , value : undefined , expected : { } } ,
15- { label : "empty string" , value : "" , expected : { } } ,
16- { label : "single pair" , value : "x-env:dev" , expected : { "x-env" : "dev" } } ,
17- {
18- label : "multiple pairs with whitespace" ,
19- value : " x-env : dev , x-team:core " ,
20- expected : { "x-env" : "dev" , "x-team" : "core" } ,
21- } ,
8+ describe ( "parseHeaderFlag" , ( ) => {
9+ test . each < { label : string ; value : string ; expected : [ string , string ] } > ( [
10+ { label : "simple pair" , value : "x-env:dev" , expected : [ "x-env" , "dev" ] } ,
2211 {
2312 label : "value containing colons (split on FIRST colon)" ,
2413 value : "authorization:Bearer abc:def" ,
25- expected : { authorization : "Bearer abc:def" } ,
14+ expected : [ " authorization" , "Bearer abc:def" ] ,
2615 } ,
27- { label : "trailing comma" , value : "x-env:dev," , expected : { "x-env" : "dev" } } ,
28- { label : "empty value" , value : "x-empty:" , expected : { "x-empty" : "" } } ,
29- ] ;
30-
31- test . each ( parseCases ) ( "parses $label" , ( { value, expected } ) => {
32- expect ( parseHeaderPairs ( value ) ) . toEqual ( expected ) ;
16+ { label : "empty value" , value : "x-empty:" , expected : [ "x-empty" , "" ] } ,
17+ { label : "trims whitespace" , value : " x-env : dev " , expected : [ "x-env" , "dev" ] } ,
18+ ] ) ( "parses $label" , ( { value, expected } ) => {
19+ expect ( parseHeaderFlag ( value ) ) . toEqual ( expected ) ;
3320 } ) ;
3421
3522 test . each ( [
3623 { label : "pair without a colon" , value : "not-a-pair" } ,
3724 { label : "pair with an empty key" , value : ":value" } ,
3825 ] ) ( "throws a usage error on $label" , ( { value } ) => {
39- expect ( ( ) => parseHeaderPairs ( value ) ) . toThrow ( CliError ) ;
26+ expect ( ( ) => parseHeaderFlag ( value ) ) . toThrow ( CliError ) ;
4027 } ) ;
4128} ) ;
4229
@@ -49,15 +36,17 @@ describe("buildForwardHeaders", () => {
4936 } ;
5037
5138 test ( "preserves delivery headers and adds extras" , ( ) => {
52- const headers = buildForwardHeaders ( eventHeaders , { "x-env" : "dev" } ) ;
39+ const extra = new Headers ( { "x-env" : "dev" } ) ;
40+ const headers = buildForwardHeaders ( eventHeaders , extra ) ;
5341
5442 expect ( headers . get ( "svix-id" ) ) . toBe ( "msg_1" ) ;
5543 expect ( headers . get ( "content-type" ) ) . toBe ( "application/json" ) ;
5644 expect ( headers . get ( "x-env" ) ) . toBe ( "dev" ) ;
5745 } ) ;
5846
5947 test ( "extras may override non-svix delivery headers" , ( ) => {
60- const headers = buildForwardHeaders ( eventHeaders , { "Content-Type" : "text/plain" } ) ;
48+ const extra = new Headers ( { "Content-Type" : "text/plain" } ) ;
49+ const headers = buildForwardHeaders ( eventHeaders , extra ) ;
6150
6251 expect ( headers . get ( "content-type" ) ) . toBe ( "text/plain" ) ;
6352 } ) ;
@@ -67,10 +56,35 @@ describe("buildForwardHeaders", () => {
6756 { label : "uppercase" , key : "SVIX-SIGNATURE" } ,
6857 { label : "mixed case" , key : "Svix-Signature" } ,
6958 ] ) ( "extras can never override svix-* headers ($label)" , ( { key } ) => {
70- const headers = buildForwardHeaders ( eventHeaders , { [ key ] : "v1,forged" } ) ;
59+ const extra = new Headers ( { [ key ] : "v1,forged" } ) ;
60+ const headers = buildForwardHeaders ( eventHeaders , extra ) ;
7161
7262 expect ( headers . get ( "svix-signature" ) ) . toBe ( "v1,abc" ) ;
7363 } ) ;
64+
65+ test ( "strips hop-by-hop headers from event headers" , ( ) => {
66+ const withHopByHop = {
67+ ...eventHeaders ,
68+ host : "svix-relay.example.com" ,
69+ connection : "keep-alive" ,
70+ "transfer-encoding" : "chunked" ,
71+ } ;
72+ const headers = buildForwardHeaders ( withHopByHop , new Headers ( ) ) ;
73+
74+ expect ( headers . has ( "host" ) ) . toBe ( false ) ;
75+ expect ( headers . has ( "connection" ) ) . toBe ( false ) ;
76+ expect ( headers . has ( "transfer-encoding" ) ) . toBe ( false ) ;
77+ expect ( headers . get ( "svix-id" ) ) . toBe ( "msg_1" ) ;
78+ } ) ;
79+
80+ test ( "allows duplicate extra headers via append" , ( ) => {
81+ const extra = new Headers ( ) ;
82+ extra . append ( "x-custom" , "first" ) ;
83+ extra . append ( "x-custom" , "second" ) ;
84+ const headers = buildForwardHeaders ( { } , extra ) ;
85+
86+ expect ( headers . get ( "x-custom" ) ) . toBe ( "first, second" ) ;
87+ } ) ;
7488} ) ;
7589
7690describe ( "forwardDelivery" , ( ) => {
@@ -95,7 +109,7 @@ describe("forwardDelivery", () => {
95109 const outcome = await forwardDelivery ( {
96110 forwardTo : "http://localhost:3000/api/webhooks" ,
97111 method : "POST" ,
98- headers : buildForwardHeaders ( { "svix-id" : "msg_1" } , { } ) ,
112+ headers : buildForwardHeaders ( { "svix-id" : "msg_1" } , new Headers ( ) ) ,
99113 body : '{"type":"user.created"}' ,
100114 } ) ;
101115
0 commit comments