Skip to content

Commit 8ebfebe

Browse files
committed
Add unit tests for HTTP request to Envoy request conversion
Signed-off-by: Pushpalanka Jayawardhana <[email protected]>
1 parent f6f9efb commit 8ebfebe

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package envoy
2+
3+
import (
4+
ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
5+
ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
6+
"github.com/stretchr/testify/assert"
7+
"google.golang.org/protobuf/types/known/structpb"
8+
"net/http"
9+
"net/url"
10+
"reflect"
11+
"strings"
12+
"testing"
13+
)
14+
15+
func TestAdaptToExtAuthRequest(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
req *http.Request
19+
metadata *ext_authz_v3_core.Metadata
20+
contextExtensions map[string]string
21+
rawBody []byte
22+
want *ext_authz_v3.CheckRequest
23+
wantErr bool
24+
}{
25+
{
26+
name: "valid request with headers and metadata",
27+
req: &http.Request{
28+
Method: "GET",
29+
Host: "example-app",
30+
URL: &url.URL{Path: "/users/profile/amal#segment?param=yes"},
31+
Header: createHeaders(map[string]string{
32+
"accept": "*/*",
33+
"user-agent": "curl/7.68.0",
34+
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a",
35+
"x-forwarded-proto": "http",
36+
":authority": "example-app",
37+
}),
38+
Proto: "HTTP/1.1",
39+
ContentLength: 100,
40+
},
41+
metadata: createFilterMetadata(map[string]map[string]string{
42+
"envoy.filters.http.header_to_metadata": {"policy_type": "ingress"},
43+
}),
44+
contextExtensions: map[string]string{
45+
"key1": "value1",
46+
"key2": "value2",
47+
},
48+
rawBody: []byte(`{"key":"value"}`),
49+
want: &ext_authz_v3.CheckRequest{
50+
Attributes: &ext_authz_v3.AttributeContext{
51+
Request: &ext_authz_v3.AttributeContext_Request{
52+
Http: &ext_authz_v3.AttributeContext_HttpRequest{
53+
Host: "example-app",
54+
Method: "GET",
55+
Path: "/users/profile/amal%23segment%3Fparam=yes", //URL encoded
56+
Headers: map[string]string{
57+
"accept": "*/*",
58+
"user-agent": "curl/7.68.0",
59+
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a",
60+
"x-forwarded-proto": "http",
61+
":authority": "example-app",
62+
},
63+
RawBody: []byte(`{"key":"value"}`),
64+
},
65+
},
66+
ContextExtensions: map[string]string{
67+
"key1": "value1",
68+
"key2": "value2",
69+
},
70+
MetadataContext: createFilterMetadata(map[string]map[string]string{
71+
"envoy.filters.http.header_to_metadata": {"policy_type": "ingress"},
72+
}),
73+
},
74+
},
75+
wantErr: false,
76+
},
77+
}
78+
79+
for _, tt := range tests {
80+
t.Run(tt.name, func(t *testing.T) {
81+
got, err := AdaptToExtAuthRequest(tt.req, tt.metadata, tt.contextExtensions, tt.rawBody)
82+
83+
// Assert error
84+
assert.Equal(t, tt.wantErr, err != nil, "Unexpected error state")
85+
86+
if err == nil {
87+
// Validate the transformed request using `want` fields
88+
assert.Equal(t, tt.want.Attributes.Request.Http.Host, got.Attributes.Request.Http.Host, "Mismatch in Host")
89+
assert.Equal(t, tt.want.Attributes.Request.Http.Method, got.Attributes.Request.Http.Method, "Mismatch in Method")
90+
assert.Equal(t, tt.want.Attributes.Request.Http.Path, got.Attributes.Request.Http.Path, "Mismatch in Path")
91+
92+
// Headers comparison
93+
assert.Equal(t, tt.want.Attributes.Request.Http.Headers, got.Attributes.Request.Http.Headers, "Mismatch in Headers")
94+
95+
// Metadata comparison
96+
assert.True(t, compareMetadata(tt.want.Attributes.MetadataContext, got.Attributes.MetadataContext), "Mismatch in MetadataContext")
97+
98+
// Context extensions comparison
99+
assert.Equal(t, tt.want.Attributes.ContextExtensions, got.Attributes.ContextExtensions, "Mismatch in ContextExtensions")
100+
101+
// RawBody comparison
102+
assert.Equal(t, tt.want.Attributes.Request.Http.RawBody, got.Attributes.Request.Http.RawBody, "Mismatch in RawBody")
103+
}
104+
})
105+
}
106+
}
107+
108+
// Helper to map headers from http.Header to a comparable map[string]string
109+
func mapHeaders(header http.Header) map[string]string {
110+
mappedHeaders := make(map[string]string)
111+
for key, values := range header {
112+
mappedHeaders[strings.ToLower(key)] = strings.Join(values, ", ")
113+
}
114+
return mappedHeaders
115+
}
116+
117+
// Helper to compare Envoy metadata objects
118+
func compareMetadata(expected *ext_authz_v3_core.Metadata, actual *ext_authz_v3_core.Metadata) bool {
119+
if len(expected.FilterMetadata) != len(actual.FilterMetadata) {
120+
return false
121+
}
122+
for key, expectedStruct := range expected.FilterMetadata {
123+
actualStruct, ok := actual.FilterMetadata[key]
124+
if !ok || !reflect.DeepEqual(expectedStruct.Fields, actualStruct.Fields) {
125+
return false
126+
}
127+
}
128+
return true
129+
}
130+
131+
// Helper to generate HTTP headers
132+
func createHeaders(headerMap map[string]string) http.Header {
133+
headers := make(http.Header)
134+
for key, value := range headerMap {
135+
headers.Add(key, value)
136+
}
137+
return headers
138+
}
139+
140+
// Helper to generate Envoy filter metadata
141+
func createFilterMetadata(filterMap map[string]map[string]string) *ext_authz_v3_core.Metadata {
142+
filterMetadata := make(map[string]*structpb.Struct)
143+
for key, subMap := range filterMap {
144+
fields := make(map[string]*structpb.Value)
145+
for subKey, subValue := range subMap {
146+
fields[subKey] = structpb.NewStringValue(subValue)
147+
}
148+
filterMetadata[key] = &structpb.Struct{Fields: fields}
149+
}
150+
return &ext_authz_v3_core.Metadata{FilterMetadata: filterMetadata}
151+
}
152+
153+
func Test_validateURLForInvalidUTF8(t *testing.T) {
154+
type args struct {
155+
u *url.URL
156+
}
157+
tests := []struct {
158+
name string
159+
args args
160+
wantErr bool
161+
errMsg string
162+
}{
163+
{
164+
name: "valid UTF-8 path and query",
165+
args: args{u: parseURL(t, "https://example.com/path?query=value")},
166+
wantErr: false,
167+
},
168+
{
169+
name: "invalid UTF-8 in path",
170+
args: args{u: parseURL(t, "https://example.com/%C3%28")}, // Invalid UTF-8 sequence
171+
wantErr: true,
172+
errMsg: `invalid utf8 in path: "/\xc3("`,
173+
},
174+
{
175+
name: "valid UTF-8 path with invalid UTF-8 in query",
176+
args: args{u: parseURL(t, "https://example.com/path?query=%C3%28")}, // Invalid UTF-8 in query
177+
wantErr: true,
178+
errMsg: `invalid utf8 in query: "query=%C3%28"`,
179+
},
180+
{
181+
name: "invalid UTF-8 in query and path",
182+
args: args{u: parseURL(t, "https://example.com/%C3%28?query=%C3%28")},
183+
wantErr: true,
184+
errMsg: `invalid utf8 in path: "/\xc3("`,
185+
},
186+
}
187+
188+
for _, tt := range tests {
189+
t.Run(tt.name, func(t *testing.T) {
190+
err := validateURLForInvalidUTF8(tt.args.u)
191+
if (err != nil) != tt.wantErr {
192+
t.Errorf("validateURLForInvalidUTF8() error = %v, wantErr %v", err, tt.wantErr)
193+
}
194+
if tt.wantErr && err != nil && !strings.Contains(err.Error(), tt.errMsg) {
195+
t.Errorf("validateURLForInvalidUTF8() error message = %v, expected to contain %v", err.Error(), tt.errMsg)
196+
}
197+
})
198+
}
199+
}
200+
201+
// Helper function to parse a URL for testing
202+
func parseURL(t *testing.T, rawurl string) *url.URL {
203+
u, err := url.Parse(rawurl)
204+
if err != nil {
205+
t.Fatalf("Failed to parse URL %s: %v", rawurl, err)
206+
}
207+
return u
208+
}

0 commit comments

Comments
 (0)