You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/agent/structs/config_entry_routes_test.go

913 lines
20 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package structs
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
)
func TestTCPRoute(t *testing.T) {
t.Parallel()
cases := map[string]configEntryTestcase{
"multiple services": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-one",
Services: []TCPService{{
Name: "foo",
}, {
Name: "bar",
}},
},
validateErr: "tcp-route currently only supports one service",
},
"normalize parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Services: []TCPService{{
Name: "foo",
}},
},
normalizeOnly: true,
check: func(t *testing.T, entry ConfigEntry) {
expectedParent := ResourceReference{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
}
route := entry.(*TCPRouteConfigEntry)
require.Len(t, route.Parents, 1)
require.Equal(t, expectedParent, route.Parents[0])
},
},
"invalid parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Kind: "route",
Name: "gateway",
}},
},
validateErr: "unsupported parent kind",
},
"duplicate parents with no listener specified": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
},
},
},
validateErr: "route parents must be unique",
},
"duplicate parents with listener specified": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
validateErr: "route parents must be unique",
},
"almost duplicate parents with one not specifying a listener": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
check: func(t *testing.T, entry ConfigEntry) {
expectedParents := []ResourceReference{
{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
{
Kind: APIGateway,
Name: "gateway",
SectionName: "same",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
}
route := entry.(*TCPRouteConfigEntry)
require.Len(t, route.Parents, 2)
require.Equal(t, expectedParents[0], route.Parents[0])
require.Equal(t, expectedParents[1], route.Parents[1])
},
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}
func TestHTTPRoute(t *testing.T) {
t.Parallel()
cases := map[string]configEntryTestcase{
"normalize parent kind": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-one",
Parents: []ResourceReference{{
Name: "gateway",
}},
},
normalizeOnly: true,
check: func(t *testing.T, entry ConfigEntry) {
expectedParent := ResourceReference{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
}
route := entry.(*HTTPRouteConfigEntry)
require.Len(t, route.Parents, 1)
require.Equal(t, expectedParent, route.Parents[0])
},
},
"invalid parent kind": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Kind: "route",
Name: "gateway",
}},
},
validateErr: "unsupported parent kind",
},
"wildcard hostnames": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"*"},
},
validateErr: "host \"*\" must not be a wildcard",
},
"wildcard subdomain": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"*.consul.example"},
},
validateErr: "host \"*.consul.example\" must not be a wildcard",
},
"valid dns hostname": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"...not legal"},
},
validateErr: "host \"...not legal\" must be a valid DNS hostname",
},
"rule matches invalid header match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Headers: []HTTPHeaderMatch{{
Match: HTTPHeaderMatchType("foo"),
Name: "foo",
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Headers[0], match type should be one of present, exact, prefix, suffix, or regex",
},
"rule matches invalid header match name": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Headers: []HTTPHeaderMatch{{
Match: HTTPHeaderMatchPresent,
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Headers[0], missing required Name field",
},
"rule matches invalid query match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Query: []HTTPQueryMatch{{
Match: HTTPQueryMatchType("foo"),
Name: "foo",
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Query[0], match type should be one of present, exact, or regex",
},
"rule matches invalid query match name": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Query: []HTTPQueryMatch{{
Match: HTTPQueryMatchPresent,
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Query[0], missing required Name field",
},
"rule matches invalid path match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Path: HTTPPathMatch{
Match: HTTPPathMatchType("foo"),
},
}},
}},
},
validateErr: "Rule[0], Match[0], Path, match type should be one of exact, prefix, or regex",
},
"rule matches invalid path match prefix": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Path: HTTPPathMatch{
Match: HTTPPathMatchPrefix,
},
}},
}},
},
validateErr: "Rule[0], Match[0], Path, prefix type match doesn't start with '/': \"\"",
},
"rule matches invalid method": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Method: HTTPMatchMethod("foo"),
}},
}},
},
validateErr: "Rule[0], Match[0], Method contains an invalid method \"FOO\"",
},
"rule normalizes method casing and path matches": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Method: HTTPMatchMethod("trace"),
}},
}},
},
},
"rule normalizes service weight": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-one",
Rules: []HTTPRouteRule{{
Services: []HTTPService{
{
Name: "test",
Weight: 0,
},
{
Name: "test2",
Weight: -1,
},
},
}},
},
check: func(t *testing.T, entry ConfigEntry) {
route := entry.(*HTTPRouteConfigEntry)
require.Equal(t, 1, route.Rules[0].Services[0].Weight)
require.Equal(t, 1, route.Rules[0].Services[1].Weight)
},
},
"duplicate parents with no listener specified": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
},
},
},
validateErr: "route parents must be unique",
},
"duplicate parents with listener specified": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
validateErr: "route parents must be unique",
},
"almost duplicate parents with one not specifying a listener": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
check: func(t *testing.T, entry ConfigEntry) {
expectedParents := []ResourceReference{
{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
{
Kind: APIGateway,
Name: "gateway",
SectionName: "same",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
}
route := entry.(*HTTPRouteConfigEntry)
require.Len(t, route.Parents, 2)
require.Equal(t, expectedParents[0], route.Parents[0])
require.Equal(t, expectedParents[1], route.Parents[1])
},
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}
func TestHTTPMatch_DeepEqual(t *testing.T) {
type fields struct {
Headers []HTTPHeaderMatch
Method HTTPMatchMethod
Path HTTPPathMatch
Query []HTTPQueryMatch
}
type args struct {
other HTTPMatch
}
tests := map[string]struct {
match HTTPMatch
other HTTPMatch
want bool
}{
"all fields equal": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: true,
},
"differing number of header matches": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
"differing header matches": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h4",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
"different path matching": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/zoidberg",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
"differing methods": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodConnect,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
"differing number of query matches": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
"different query matches": {
match: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "another",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
other: HTTPMatch{
Headers: []HTTPHeaderMatch{
{
Match: HTTPHeaderMatchExact,
Name: "h1",
Value: "a",
},
{
Match: HTTPHeaderMatchPrefix,
Name: "h2",
Value: "b",
},
},
Method: HTTPMatchMethodGet,
Path: HTTPPathMatch{
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
Value: "/bender",
},
Query: []HTTPQueryMatch{
{
Match: HTTPQueryMatchExact,
Name: "q",
Value: "nibbler",
},
{
Match: HTTPQueryMatchPresent,
Name: "ship",
Value: "planet express",
},
},
},
want: false,
},
}
for name, tt := range tests {
name := name
tt := tt
t.Run(name, func(t *testing.T) {
t.Parallel()
if got := tt.match.DeepEqual(tt.other); got != tt.want {
t.Errorf("HTTPMatch.DeepEqual() = %v, want %v", got, tt.want)
}
})
}
}