package http import ( "context" "fmt" "net/http" "testing" "forge.cadoles.com/cadoles/bouncer/internal/rule" "github.com/pkg/errors" ) func TestSetRequestHost(t *testing.T) { type Vars struct { NewHost string `expr:"newHost"` } engine := createRuleEngine[Vars](t, rule.WithExpr(setRequestHostFunc()), rule.WithRules( "set_host(ctx, vars.newHost)", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } ctx := context.Background() ctx = WithRequest(ctx, req) vars := Vars{ NewHost: "foobar", } if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if e, g := vars.NewHost, req.Host; e != g { t.Errorf("req.Host: expected '%v', got '%v'", e, g) } } func TestSetRequestURL(t *testing.T) { type Vars struct { NewURL string `expr:"newURL"` } engine := createRuleEngine[Vars](t, rule.WithExpr(setRequestURLFunc()), rule.WithRules( "set_url(ctx, vars.newURL)", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } ctx := context.Background() ctx = WithRequest(ctx, req) vars := Vars{ NewURL: "http://localhost", } if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if e, g := vars.NewURL, req.URL.String(); e != g { t.Errorf("req.URL.String(): expected '%v', got '%v'", e, g) } } func TestAddRequestHeader(t *testing.T) { type Vars struct { NewHeaderKey string `expr:"newHeaderKey"` NewHeaderValue string `expr:"newHeaderValue"` } engine := createRuleEngine[Vars](t, rule.WithExpr(addRequestHeaderFunc()), rule.WithRules( "add_header(ctx, vars.newHeaderKey, vars.newHeaderValue)", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } ctx := context.Background() ctx = WithRequest(ctx, req) vars := Vars{ NewHeaderKey: "X-My-Header", NewHeaderValue: "foobar", } if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if e, g := vars.NewHeaderValue, req.Header.Get(vars.NewHeaderKey); e != g { t.Errorf("req.Header.Get(vars.NewHeaderKey): expected '%v', got '%v'", e, g) } } func TestSetRequestHeader(t *testing.T) { type Vars struct { HeaderKey string `expr:"headerKey"` HeaderValue string `expr:"headerValue"` } engine := createRuleEngine[Vars](t, rule.WithExpr(setRequestHeaderFunc()), rule.WithRules( "set_header(ctx, vars.headerKey, vars.headerValue)", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } vars := Vars{ HeaderKey: "X-My-Header", HeaderValue: "foobar", } req.Header.Set(vars.HeaderKey, "test") ctx := context.Background() ctx = WithRequest(ctx, req) if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if e, g := vars.HeaderValue, req.Header.Get(vars.HeaderKey); e != g { t.Errorf("req.Header.Get(vars.HeaderKey): expected '%v', got '%v'", e, g) } } func TestDelRequestHeaders(t *testing.T) { type Vars struct { HeaderPattern string `expr:"headerPattern"` } engine := createRuleEngine[Vars](t, rule.WithExpr(delRequestHeadersFunc()), rule.WithRules( "del_headers(ctx, vars.headerPattern)", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } vars := Vars{ HeaderPattern: "X-My-*", } req.Header.Set("X-My-Header", "test") ctx := context.Background() ctx = WithRequest(ctx, req) if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if val := req.Header.Get("X-My-Header"); val != "" { t.Errorf("req.Header.Get(\"X-My-Header\") should be empty, got '%v'", val) } } func TestAddRequestCookie(t *testing.T) { type TestCase struct { Cookie map[string]any Check func(t *testing.T, tc TestCase, req *http.Request) ShouldFail bool } testCases := []TestCase{ { Cookie: map[string]any{ "name": "test", }, Check: func(t *testing.T, tc TestCase, req *http.Request) { cookie, err := req.Cookie(tc.Cookie["name"].(string)) if err != nil { t.Errorf("%+v", errors.WithStack(err)) return } if e, g := tc.Cookie["name"], cookie.Name; e != g { t.Errorf("cookie.Name: expected '%v', got '%v'", e, g) } }, }, { Cookie: map[string]any{ "name": "foo", "value": "test", }, Check: func(t *testing.T, tc TestCase, req *http.Request) { cookie, err := req.Cookie(tc.Cookie["name"].(string)) if err != nil { t.Errorf("%+v", errors.WithStack(err)) return } if e, g := tc.Cookie["name"], cookie.Name; e != g { t.Errorf("cookie.Name: expected '%v', got '%v'", e, g) } if e, g := tc.Cookie["value"], cookie.Value; e != g { t.Errorf("cookie.Value: expected '%v', got '%v'", e, g) } }, }, } for idx, tc := range testCases { t.Run(fmt.Sprintf("Case_%d", idx), func(t *testing.T) { type Vars struct { NewCookie map[string]any `expr:"new_cookie"` } engine := createRuleEngine[Vars](t, rule.WithExpr(addRequestCookieFunc()), rule.WithRules( `add_cookie(ctx, vars.new_cookie)`, ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } vars := Vars{ NewCookie: tc.Cookie, } ctx := context.Background() ctx = WithRequest(ctx, req) if _, err := engine.Apply(ctx, vars); err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if tc.ShouldFail { t.Error("engine.Apply() should have failed") } if tc.Check != nil { tc.Check(t, tc, req) } }) } } func TestGetRequestCookie(t *testing.T) { type Vars struct { CookieName string `expr:"cookieName"` } engine := createRuleEngine[Vars](t, rule.WithExpr(getRequestCookieFunc()), rule.WithRules( "let cookie = get_cookie(ctx, vars.cookieName); cookie.value", ), ) req, err := http.NewRequest("GET", "http://example.net", nil) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } vars := Vars{ CookieName: "foo", } cookie := &http.Cookie{ Name: vars.CookieName, Value: "bar", } req.AddCookie(cookie) ctx := context.Background() ctx = WithRequest(ctx, req) results, err := engine.Apply(ctx, vars) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if e, g := cookie.Value, results[0]; e != g { t.Errorf("result[0]: expected '%v', got '%v'", e, g) } } func createRuleEngine[V any](t *testing.T, funcs ...rule.OptionFunc) *rule.Engine[V] { engine, err := rule.NewEngine[V](funcs...) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } return engine }