diff --git a/test/e2e/httpproxy/001_required_field_validation_test.go b/test/e2e/httpproxy/001_required_field_validation_test.go new file mode 100644 index 00000000000..18dd63873ee --- /dev/null +++ b/test/e2e/httpproxy/001_required_field_validation_test.go @@ -0,0 +1,150 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build e2e + +package httpproxy + +import ( + "context" + + contourv1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func testRequiredFieldValidation(fx *e2e.Framework) { + t := fx.T() + ns := "001-required-field-validation" + + fx.CreateNamespace(ns) + defer fx.DeleteNamespace(ns) + + // This HTTPProxy is expressed as an Unstructured because the JSON + // tags for the relevant field do not include "omitempty", so when + // a typed Go struct is serialized to JSON the field *is* included + // and therefore does not fail validation. + missingConditionHeaderName := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "projectcontour.io/v1", + "kind": "HTTPProxy", + "metadata": map[string]interface{}{ + "name": "missing-condition-header-name", + "namespace": ns, + }, + "spec": map[string]interface{}{ + "routes": []map[string]interface{}{ + { + "conditions": []map[string]interface{}{ + { + "header": map[string]interface{}{ + "present": true, + }, + }, + }, + "services": []map[string]interface{}{ + { + "name": "foo", + "port": 80, + }, + }, + }, + }, + }, + }, + } + + err := fx.Client.Create(context.TODO(), missingConditionHeaderName) + require.Error(t, err) + assert.Contains(t, err.Error(), "spec.routes.conditions.header.name: Required value") + + // This HTTPProxy is expressed as an Unstructured because the JSON + // tags for the relevant field do not include "omitempty", so when + // a typed Go struct is serialized to JSON the field *is* included + // and therefore does not fail validation. + missingVirtualHostName := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "projectcontour.io/v1", + "kind": "HTTPProxy", + "metadata": map[string]interface{}{ + "name": "missing-virtualhost-name", + "namespace": ns, + }, + "spec": map[string]interface{}{ + "virtualhost": map[string]interface{}{ + "tls": map[string]interface{}{ + "passthrough": true, + }, + }, + }, + }, + } + + err = fx.Client.Create(context.TODO(), missingVirtualHostName) + require.Error(t, err) + assert.Contains(t, err.Error(), "spec.virtualhost.fqdn: Required value") + + // This HTTPProxy is expressed as an Unstructured because the JSON + // tags for the relevant field do not include "omitempty", so when + // a typed Go struct is serialized to JSON the field *is* included + // and therefore does not fail validation. + missingIncludesName := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "projectcontour.io/v1", + "kind": "HTTPProxy", + "metadata": map[string]interface{}{ + "name": "missing-includes-name", + "namespace": ns, + }, + "spec": map[string]interface{}{ + "includes": []map[string]interface{}{ + { + "namespace": "foo", + }, + }, + }, + }, + } + + err = fx.Client.Create(context.TODO(), missingIncludesName) + require.Error(t, err) + assert.Contains(t, err.Error(), "spec.includes.name: Required value") + + servicePortRange := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: "service-port-range", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "ports.projectcontour.io", + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "any-service-name", + Port: 80000, + }, + }, + }, + }, + }, + } + err = fx.Client.Create(context.TODO(), servicePortRange) + require.Error(t, err) + assert.Contains(t, err.Error(), "spec.routes.services.port: Invalid value") +} diff --git a/test/e2e/httpproxy/011_retry_policy_validation_test.go b/test/e2e/httpproxy/011_retry_policy_validation_test.go new file mode 100644 index 00000000000..eab6a3885f0 --- /dev/null +++ b/test/e2e/httpproxy/011_retry_policy_validation_test.go @@ -0,0 +1,61 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build e2e + +package httpproxy + +import ( + "context" + + contourv1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func testRetryPolicyValidation(fx *e2e.Framework) { + t := fx.T() + ns := "011-retry-policy-validation" + + fx.CreateNamespace(ns) + defer fx.DeleteNamespace(ns) + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: "invalid-retry-on-condition", + }, + Spec: contourv1.HTTPProxySpec{ + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "foo", + Port: 80, + }, + }, + RetryPolicy: &contourv1.RetryPolicy{ + RetryOn: []contourv1.RetryOn{ + "foobar", + }, + }, + }, + }, + }, + } + err := fx.Client.Create(context.TODO(), p) + require.Error(t, err) + assert.Contains(t, err.Error(), "spec.routes.retryPolicy.retryOn: Unsupported value") +} diff --git a/test/e2e/httpproxy/httpproxy_test.go b/test/e2e/httpproxy/httpproxy_test.go index 013dbeb0644..edb41e0b6c7 100644 --- a/test/e2e/httpproxy/httpproxy_test.go +++ b/test/e2e/httpproxy/httpproxy_test.go @@ -34,6 +34,9 @@ var _ = Describe("HTTPProxy", func() { f = e2e.NewFramework(GinkgoT()) }) + It("001-required-field-validation", func() { + testRequiredFieldValidation(f) + }) It("002-header-condition-match", func() { testHeaderConditionMatch(f) }) @@ -58,6 +61,9 @@ var _ = Describe("HTTPProxy", func() { It("010-include-prefix-condition", func() { testIncludePrefixCondition(f) }) + It("011-retry-policy-validation", func() { + testRetryPolicyValidation(f) + }) It("012-https-fallback-certificate", func() { testHTTPSFallbackCertificate(f) })