Skip to content

Add CEL validation tests for NginxProxy #3701

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

Merged
merged 23 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9631d54
Add tests for kubernetes validation
shaun-nx Aug 7, 2025
8455a14
Add tests for rewriteClientIP
shaun-nx Aug 7, 2025
8404b7d
Move validation function to common.go
shaun-nx Aug 7, 2025
0ba4b7c
Add comment to `validateCrd` function
shaun-nx Aug 7, 2025
8032352
Merge branch 'main' into tests/cel-nginxproxy
shaun-nx Aug 7, 2025
82f84a1
Merge branch 'main' into tests/cel-nginxproxy
shaun-nx Aug 11, 2025
b1da498
Move expected error strings to common.go
shaun-nx Aug 11, 2025
80e121a
Add tests for autoscaling validation
shaun-nx Aug 11, 2025
9bbe4cd
Refactor tests
shaun-nx Aug 11, 2025
8612c21
Change name of `testResourceName` const
shaun-nx Aug 11, 2025
45302dc
Group expexted error consts
shaun-nx Aug 11, 2025
9bb4bdb
Ensure gateway CRDs are installed when running `test-cel-validation`
shaun-nx Aug 11, 2025
531850d
Undo changes to `install-gateway-crds` command
shaun-nx Aug 11, 2025
d742467
Change `policyName` to `resourceName`
shaun-nx Aug 11, 2025
216406c
Add test for when `minReplicas` is nil
shaun-nx Aug 12, 2025
702ccb5
Merge branch 'main' into tests/cel-nginxproxy
shaun-nx Aug 12, 2025
960db80
Have `k8sClient.Delete` use same context and `k8sClient.Create`
shaun-nx Aug 12, 2025
38e20ee
Add spacing
shaun-nx Aug 12, 2025
aba710a
Add additional comments
shaun-nx Aug 12, 2025
14ee3f7
Ensure installed resources are only delete if an error is NOT expected
shaun-nx Aug 12, 2025
ac12bab
Expected k8sClient.Delete to succeed
shaun-nx Aug 12, 2025
55d4fe1
Merge branch 'main' into tests/cel-nginxproxy
shaun-nx Aug 13, 2025
51d49f8
Add additional comments
shaun-nx Aug 13, 2025
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
119 changes: 47 additions & 72 deletions tests/cel/clientsettingspolicy_test.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
package cel

import (
"context"
"testing"

. "github.com/onsi/gomega"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

ngfAPIv1alpha1 "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1"
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/helpers"
"github.com/nginx/nginx-gateway-fabric/v2/tests/framework"
)

func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
t.Parallel()
g := NewWithT(t)

k8sClient, err := getKubernetesClient(t)
g.Expect(err).ToNot(HaveOccurred())
k8sClient := getKubernetesClient(t)

tests := []struct {
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec
spec ngfAPIv1alpha1.ClientSettingsPolicySpec
name string
wantErrors []string
}{
{
name: "Validate TargetRef of kind Gateway is allowed",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: gatewayGroup,
Expand All @@ -37,7 +30,7 @@ func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
},
{
name: "Validate TargetRef of kind HTTPRoute is allowed",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: httpRouteKind,
Group: gatewayGroup,
Expand All @@ -46,7 +39,7 @@ func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
},
{
name: "Validate TargetRef of kind GRPCRoute is allowed",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: grpcRouteKind,
Group: gatewayGroup,
Expand All @@ -56,7 +49,7 @@ func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
{
name: "Validate Invalid TargetRef Kind is not allowed",
wantErrors: []string{expectedTargetRefKindError},
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: invalidKind,
Group: gatewayGroup,
Expand All @@ -66,7 +59,7 @@ func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
{
name: "Validate TCPRoute TargetRef Kind is not allowed",
wantErrors: []string{expectedTargetRefKindError},
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: tcpRouteKind,
Group: gatewayGroup,
Expand All @@ -78,26 +71,32 @@ func TestClientSettingsPoliciesTargetRefKind(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
validateClientSettingsPolicy(t, tt, g, k8sClient)
spec := tt.spec
spec.TargetRef.Name = gatewayv1alpha2.ObjectName(uniqueResourceName(testTargetRefName))
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{
ObjectMeta: controllerruntime.ObjectMeta{
Name: uniqueResourceName(testResourceName),
Namespace: defaultNamespace,
},
Spec: spec,
}
validateCrd(t, tt.wantErrors, clientSettingsPolicy, k8sClient)
})
}
}

func TestClientSettingsPoliciesTargetRefGroup(t *testing.T) {
t.Parallel()
g := NewWithT(t)

k8sClient, err := getKubernetesClient(t)
g.Expect(err).ToNot(HaveOccurred())
k8sClient := getKubernetesClient(t)

tests := []struct {
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec
spec ngfAPIv1alpha1.ClientSettingsPolicySpec
name string
wantErrors []string
}{
{
name: "Validate gateway.networking.k8s.io TargetRef Group is allowed",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: gatewayGroup,
Expand All @@ -107,7 +106,7 @@ func TestClientSettingsPoliciesTargetRefGroup(t *testing.T) {
{
name: "Validate invalid.networking.k8s.io TargetRef Group is not allowed",
wantErrors: []string{expectedTargetRefGroupError},
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: invalidGroup,
Expand All @@ -117,7 +116,7 @@ func TestClientSettingsPoliciesTargetRefGroup(t *testing.T) {
{
name: "Validate discovery.k8s.io/v1 TargetRef Group is not allowed",
wantErrors: []string{expectedTargetRefGroupError},
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: discoveryGroup,
Expand All @@ -129,26 +128,32 @@ func TestClientSettingsPoliciesTargetRefGroup(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
validateClientSettingsPolicy(t, tt, g, k8sClient)
spec := tt.spec
spec.TargetRef.Name = gatewayv1alpha2.ObjectName(uniqueResourceName(testTargetRefName))
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{
ObjectMeta: controllerruntime.ObjectMeta{
Name: uniqueResourceName(testResourceName),
Namespace: defaultNamespace,
},
Spec: spec,
}
validateCrd(t, tt.wantErrors, clientSettingsPolicy, k8sClient)
})
}
}

func TestClientSettingsPoliciesKeepAliveTimeout(t *testing.T) {
t.Parallel()
g := NewWithT(t)

k8sClient, err := getKubernetesClient(t)
g.Expect(err).ToNot(HaveOccurred())
k8sClient := getKubernetesClient(t)

tests := []struct {
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec
spec ngfAPIv1alpha1.ClientSettingsPolicySpec
name string
wantErrors []string
}{
{
name: "Validate KeepAliveTimeout is not set",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: gatewayGroup,
Expand All @@ -158,7 +163,7 @@ func TestClientSettingsPoliciesKeepAliveTimeout(t *testing.T) {
},
{
name: "Validate KeepAlive is set",
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: gatewayGroup,
Expand All @@ -174,7 +179,7 @@ func TestClientSettingsPoliciesKeepAliveTimeout(t *testing.T) {
{
name: "Validate Header cannot be set without Server",
wantErrors: []string{expectedHeaderWithoutServerError},
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{
Kind: gatewayKind,
Group: gatewayGroup,
Expand All @@ -191,46 +196,16 @@ func TestClientSettingsPoliciesKeepAliveTimeout(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
validateClientSettingsPolicy(t, tt, g, k8sClient)
spec := tt.spec
spec.TargetRef.Name = gatewayv1alpha2.ObjectName(uniqueResourceName(testTargetRefName))
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{
ObjectMeta: controllerruntime.ObjectMeta{
Name: uniqueResourceName(testResourceName),
Namespace: defaultNamespace,
},
Spec: spec,
}
validateCrd(t, tt.wantErrors, clientSettingsPolicy, k8sClient)
})
}
}

func validateClientSettingsPolicy(t *testing.T, tt struct {
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec
name string
wantErrors []string
}, g *WithT, k8sClient client.Client,
) {
t.Helper()

policySpec := tt.policySpec
policySpec.TargetRef.Name = gatewayv1alpha2.ObjectName(uniqueResourceName(testTargetRefName))
policyName := uniqueResourceName(testPolicyName)

clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{
ObjectMeta: controllerruntime.ObjectMeta{
Name: policyName,
Namespace: defaultNamespace,
},
Spec: policySpec,
}
timeoutConfig := framework.DefaultTimeoutConfig()
ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.KubernetesClientTimeout)
err := k8sClient.Create(ctx, clientSettingsPolicy)
defer cancel()

// Clean up after test
defer func() {
_ = k8sClient.Delete(context.Background(), clientSettingsPolicy)
}()

if len(tt.wantErrors) == 0 {
g.Expect(err).ToNot(HaveOccurred())
} else {
g.Expect(err).To(HaveOccurred())
for _, wantError := range tt.wantErrors {
g.Expect(err.Error()).To(ContainSubstring(wantError), "Expected error '%s' not found in: %s", wantError, err.Error())
}
}
}
63 changes: 47 additions & 16 deletions tests/cel/common.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package cel

import (
"context"
"crypto/rand"
"fmt"
"testing"

. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

ngfAPIv1alpha1 "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1"
ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha2"
"github.com/nginx/nginx-gateway-fabric/v2/tests/framework"
)

const (
Expand All @@ -28,39 +31,43 @@ const (
)

const (
expectedTargetRefKindError = `TargetRef Kind must be one of: Gateway, HTTPRoute, or GRPCRoute`
expectedTargetRefGroupError = `TargetRef Group must be gateway.networking.k8s.io.`
expectedHeaderWithoutServerError = `header can only be specified if server is specified`
expectedTargetRefKindError = `TargetRef Kind must be one of: Gateway, HTTPRoute, or GRPCRoute`
expectedTargetRefGroupError = `TargetRef Group must be gateway.networking.k8s.io.`
expectedHeaderWithoutServerError = `header can only be specified if server is specified`
expectedOneOfDeploymentOrDaemonSetError = `only one of deployment or daemonSet can be set`
expectedIfModeSetTrustedAddressesError = `if mode is set, trustedAddresses is a required field`
expectedMinReplicasLessThanOrEqualError = `minReplicas must be less than or equal to maxReplicas`
)

const (
defaultNamespace = "default"
)

const (
testPolicyName = "test-policy"
testResourceName = "test-resource"
testTargetRefName = "test-targetRef"
)

// getKubernetesClient returns a client connected to a real Kubernetes cluster.
func getKubernetesClient(t *testing.T) (k8sClient client.Client, err error) {
func getKubernetesClient(t *testing.T) (k8sClient client.Client) {
t.Helper()
g := NewWithT(t)
// Use controller-runtime to get cluster connection
k8sConfig, err := controllerruntime.GetConfig()
if err != nil {
return nil, err
}
g.Expect(err).ToNot(HaveOccurred())

// Set up scheme with NGF types
scheme := runtime.NewScheme()
if err = ngfAPIv1alpha1.AddToScheme(scheme); err != nil {
return nil, err
}
if err = ngfAPIv1alpha2.AddToScheme(scheme); err != nil {
return nil, err
}
// Create a new client with the scheme and return it
return client.New(k8sConfig, client.Options{Scheme: scheme})
err = ngfAPIv1alpha1.AddToScheme(scheme)
g.Expect(err).ToNot(HaveOccurred())

err = ngfAPIv1alpha2.AddToScheme(scheme)
g.Expect(err).ToNot(HaveOccurred())

k8sClient, err = client.New(k8sConfig, client.Options{Scheme: scheme})
g.Expect(err).ToNot(HaveOccurred())

return k8sClient
}

// randomPrimeNumber generates a random prime number of 64 bits.
Expand All @@ -77,3 +84,27 @@ func randomPrimeNumber() int64 {
func uniqueResourceName(name string) string {
return fmt.Sprintf("%s-%d", name, randomPrimeNumber())
}

// validateCrd creates a k8s resource and validates it against the expected errors.
func validateCrd(t *testing.T, wantErrors []string, crd client.Object, k8sClient client.Client) {
t.Helper()
g := NewWithT(t)
timeoutConfig := framework.DefaultTimeoutConfig()
ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.KubernetesClientTimeout)
err := k8sClient.Create(ctx, crd)
defer cancel()

// Clean up after test
defer func() {
_ = k8sClient.Delete(context.Background(), crd)
}()

if len(wantErrors) == 0 {
g.Expect(err).ToNot(HaveOccurred())
} else {
g.Expect(err).To(HaveOccurred())
for _, wantError := range wantErrors {
g.Expect(err.Error()).To(ContainSubstring(wantError), "Expected error '%s' not found in: %s", wantError, err.Error())
}
}
}
Loading
Loading