Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for HTTP request to Envoy request conversion #3316

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions filters/openpolicyagent/internal/envoy/skipperadapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package envoy

import (
ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
"net/http"
"net/url"
"reflect"
"strings"
"testing"
)

func TestAdaptToExtAuthRequest(t *testing.T) {
tests := []struct {
name string
req *http.Request
metadata *ext_authz_v3_core.Metadata
contextExtensions map[string]string
rawBody []byte
want *ext_authz_v3.CheckRequest
wantErr bool
}{
{
name: "valid request with headers and metadata",
req: &http.Request{
Method: "GET",
Host: "example-app",
URL: &url.URL{Path: "/users/profile/amal#segment?param=yes"},
Header: createHeaders(map[string]string{
"accept": "*/*",
"user-agent": "curl/7.68.0",
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a",
"x-forwarded-proto": "http",
":authority": "example-app",
}),
Proto: "HTTP/1.1",
ContentLength: 100,
},
metadata: createFilterMetadata(map[string]map[string]string{
"envoy.filters.http.header_to_metadata": {"policy_type": "ingress"},
}),
contextExtensions: map[string]string{
"key1": "value1",
"key2": "value2",
},
rawBody: []byte(`{"key":"value"}`),
want: &ext_authz_v3.CheckRequest{
Attributes: &ext_authz_v3.AttributeContext{
Request: &ext_authz_v3.AttributeContext_Request{
Http: &ext_authz_v3.AttributeContext_HttpRequest{
Host: "example-app",
Method: "GET",
Path: "/users/profile/amal%23segment%3Fparam=yes", //URL encoded
Headers: map[string]string{
"accept": "*/*",
"user-agent": "curl/7.68.0",
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a",
"x-forwarded-proto": "http",
":authority": "example-app",
},
RawBody: []byte(`{"key":"value"}`),
},
},
ContextExtensions: map[string]string{
"key1": "value1",
"key2": "value2",
},
MetadataContext: createFilterMetadata(map[string]map[string]string{
"envoy.filters.http.header_to_metadata": {"policy_type": "ingress"},
}),
},
},
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := AdaptToExtAuthRequest(tt.req, tt.metadata, tt.contextExtensions, tt.rawBody)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make more sense to change the tests to convert this as well to an AST input and validate that one. The Envoy request object itself is sth intermediate.

Looking forward we could also remove the double serialisation / deserialisation of Skipper Request -> Envoy Protobuf -> JSON -> OPA AST without changing the tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree!
Normally this should be part of some bdd style test with invalid input. If it is too complicated a unit test is fine.


// Assert error
assert.Equal(t, tt.wantErr, err != nil, "Unexpected error state")

if err == nil {
// Validate the transformed request using `want` fields
assert.Equal(t, tt.want.Attributes.Request.Http.Host, got.Attributes.Request.Http.Host, "Mismatch in Host")
assert.Equal(t, tt.want.Attributes.Request.Http.Method, got.Attributes.Request.Http.Method, "Mismatch in Method")
assert.Equal(t, tt.want.Attributes.Request.Http.Path, got.Attributes.Request.Http.Path, "Mismatch in Path")

// Headers comparison
assert.Equal(t, tt.want.Attributes.Request.Http.Headers, got.Attributes.Request.Http.Headers, "Mismatch in Headers")

// Metadata comparison
assert.True(t, compareMetadata(tt.want.Attributes.MetadataContext, got.Attributes.MetadataContext), "Mismatch in MetadataContext")

// Context extensions comparison
assert.Equal(t, tt.want.Attributes.ContextExtensions, got.Attributes.ContextExtensions, "Mismatch in ContextExtensions")

// RawBody comparison
assert.Equal(t, tt.want.Attributes.Request.Http.RawBody, got.Attributes.Request.Http.RawBody, "Mismatch in RawBody")
}
})
}
}

// Helper to compare Envoy metadata objects
func compareMetadata(expected *ext_authz_v3_core.Metadata, actual *ext_authz_v3_core.Metadata) bool {
if len(expected.FilterMetadata) != len(actual.FilterMetadata) {
return false
}
for key, expectedStruct := range expected.FilterMetadata {
actualStruct, ok := actual.FilterMetadata[key]
if !ok || !reflect.DeepEqual(expectedStruct.Fields, actualStruct.Fields) {
return false
}
}
return true
}

// Helper to generate HTTP headers
func createHeaders(headerMap map[string]string) http.Header {
headers := make(http.Header)
for key, value := range headerMap {
headers.Add(key, value)
}
return headers
}

// Helper to generate Envoy filter metadata
func createFilterMetadata(filterMap map[string]map[string]string) *ext_authz_v3_core.Metadata {
filterMetadata := make(map[string]*structpb.Struct)
for key, subMap := range filterMap {
fields := make(map[string]*structpb.Value)
for subKey, subValue := range subMap {
fields[subKey] = structpb.NewStringValue(subValue)
}
filterMetadata[key] = &structpb.Struct{Fields: fields}
}
return &ext_authz_v3_core.Metadata{FilterMetadata: filterMetadata}
}

func Test_validateURLForInvalidUTF8(t *testing.T) {
type args struct {
u *url.URL
}
tests := []struct {
name string
args args
wantErr bool
errMsg string
}{
{
name: "valid UTF-8 path and query",
args: args{u: parseURL(t, "https://example.com/path?query=value")},
wantErr: false,
},
{
name: "invalid UTF-8 in path",
args: args{u: parseURL(t, "https://example.com/%C3%28")}, // Invalid UTF-8 sequence
wantErr: true,
errMsg: `invalid utf8 in path: "/\xc3("`,
},
{
name: "valid UTF-8 path with invalid UTF-8 in query",
args: args{u: parseURL(t, "https://example.com/path?query=%C3%28")}, // Invalid UTF-8 in query
wantErr: true,
errMsg: `invalid utf8 in query: "query=%C3%28"`,
},
{
name: "invalid UTF-8 in query and path",
args: args{u: parseURL(t, "https://example.com/%C3%28?query=%C3%28")},
wantErr: true,
errMsg: `invalid utf8 in path: "/\xc3("`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateURLForInvalidUTF8(tt.args.u)
if (err != nil) != tt.wantErr {
t.Errorf("validateURLForInvalidUTF8() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wantErr && err != nil && !strings.Contains(err.Error(), tt.errMsg) {
t.Errorf("validateURLForInvalidUTF8() error message = %v, expected to contain %v", err.Error(), tt.errMsg)
}
})
}
}

// Helper function to parse a URL for testing
func parseURL(t *testing.T, rawurl string) *url.URL {
u, err := url.Parse(rawurl)
if err != nil {
t.Fatalf("Failed to parse URL %s: %v", rawurl, err)
}
return u
}
44 changes: 24 additions & 20 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/zalando/skipper

replace github.com/open-policy-agent/opa-envoy-plugin => github.com/Pushpalanka/opa-envoy-plugin v0.70.0-envoy-2-removed-json

require (
github.com/AlexanderYastrebov/noleak v0.0.0-20230711175737-345842f874fb
github.com/MicahParks/keyfunc v1.9.0
Expand All @@ -17,7 +19,7 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598
github.com/docker/go-connections v0.5.0
github.com/envoyproxy/go-control-plane v0.13.0
github.com/envoyproxy/go-control-plane v0.13.1
github.com/ghodss/yaml v1.0.0
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/google/go-cmp v0.6.0
Expand All @@ -27,7 +29,7 @@ require (
github.com/lightstep/lightstep-tracer-go v0.26.0
github.com/miekg/dns v1.1.62
github.com/oklog/ulid v1.3.1
github.com/open-policy-agent/opa v0.68.0
github.com/open-policy-agent/opa v0.70.0
github.com/open-policy-agent/opa-envoy-plugin v0.68.0-envoy-4
github.com/opentracing/basictracer-go v1.1.0
github.com/opentracing/opentracing-go v1.2.0
Expand Down Expand Up @@ -63,23 +65,23 @@ require (
)

require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/agnivade/levenshtein v1.2.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b // indirect
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
github.com/containerd/containerd v1.7.21 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
github.com/containerd/containerd v1.7.23 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
Expand All @@ -91,7 +93,7 @@ require (
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
Expand All @@ -101,7 +103,7 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.1 // indirect
github.com/golang/glog v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand Down Expand Up @@ -161,26 +163,28 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
golang.org/x/mod v0.20.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/tools v0.28.0 // indirect
gonum.org/v1/gonum v0.8.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.66.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/grpc v1.69.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
oras.land/oras-go/v2 v2.3.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

go 1.22
go 1.22.7

toolchain go1.23.3
Loading
Loading