-
Notifications
You must be signed in to change notification settings - Fork 129
Add CEL validation test for targetRef in ClientSettingsPolicy #3623
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
base: main
Are you sure you want to change the base?
Changes from all commits
dbb5b5c
48ec86b
c7129bd
87a4e0e
29f201e
d921ed2
f22269a
7fddffa
dd66a79
d664f35
cd9bada
7d9989d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
package cel | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" | ||
|
||
ngfAPIv1alpha1 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha1" | ||
) | ||
|
||
func TestClientSettingsPoliciesTargetRefKind(t *testing.T) { | ||
// Test valid and invalid TargetRef Kind | ||
// Valid kinds are: Gateway, HTTPRoute, GRPCRoute | ||
testValidTargetRefKind(t) | ||
// Invalid kinds should return an error | ||
// Example of an invalid kind: "InvalidKind", "TCPRoute" | ||
testInvalidTargetRefKind(t) | ||
} | ||
|
||
func TestClientSettingsPoliciesTargetRefGroup(t *testing.T) { | ||
// Test valid TargetRef Group | ||
// Valid group is: "gateway.networking.k8s.io" | ||
testValidTargetRefGroup(t) | ||
// Invalid groups should return an error | ||
// Examples of invalid groups: "invalid.networking.k8s.io", "discovery.k8s.io/v1" | ||
testInvalidTargetRefGroup(t) | ||
} | ||
|
||
func testValidTargetRefKind(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you combine testValid…, testInvalid…, etc. into one test block? It seems redundant to have multiple functions that are doing the same thing basically, the valid and invalid can be determined by the name of the the test right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like to do this to avoid having a single test function with a massive set of table tests. I find it makes the tests easier to read a digest. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps, a helper function that runs the tests would be useful for readability There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where each of the functions like testValidTargetRefKind just defines the tests and calls the helper There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also agree with @sarthyparty on combining the invalid/valid tests mainly to maintain consistency with the existing tests in our repo and because it follows standards set in the gateway api, example of their test structure: https://github.com/kubernetes-sigs/gateway-api/blob/main/pkg/test/cel/httproute_test.go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bjee19 that's a fair point. I'll make that update today as well then. Thanks :) |
||
t.Helper() | ||
|
||
tests := []struct { | ||
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec | ||
name string | ||
wantErrors []string | ||
}{ | ||
{ | ||
name: "Validate TargetRef of kind Gateway is allowed", | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "Gateway", | ||
Group: "gateway.networking.k8s.io", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could make this group value a constant There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point. |
||
}, | ||
}, | ||
}, | ||
{ | ||
name: "Validate TargetRef of kind HTTPRoute is allowed", | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "HTTPRoute", | ||
Group: "gateway.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "Validate TargetRef of kind GRPCRoute is allowed", | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "GRPCRoute", | ||
Group: "gateway.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a ClientSettingsPolicy with the targetRef from the test case. | ||
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-policy", | ||
Namespace: "default", | ||
}, | ||
Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: tt.policySpec.TargetRef, | ||
}, | ||
} | ||
validateClientSettingsPolicy(t, clientSettingsPolicy, tt.wantErrors) | ||
}) | ||
} | ||
} | ||
|
||
func testInvalidTargetRefKind(t *testing.T) { | ||
t.Helper() | ||
|
||
tests := []struct { | ||
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec | ||
name string | ||
wantErrors []string | ||
}{ | ||
{ | ||
name: "Validate Invalid TargetRef Kind is not allowed", | ||
wantErrors: []string{"TargetRef Kind must be one of: Gateway, HTTPRoute, or GRPCRoute'"}, | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "InvalidKind", | ||
Group: "gateway.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "Validate TCPRoute TargetRef Kind is not allowed", | ||
wantErrors: []string{"TargetRef Kind must be one of: Gateway, HTTPRoute, or GRPCRoute'"}, | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "TCPRoute", | ||
Group: "gateway.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a ClientSettingsPolicy with the targetRef from the test case. | ||
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-policy", | ||
Namespace: "default", | ||
}, | ||
Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: tt.policySpec.TargetRef, | ||
}, | ||
} | ||
validateClientSettingsPolicy(t, clientSettingsPolicy, tt.wantErrors) | ||
}) | ||
} | ||
} | ||
|
||
func testValidTargetRefGroup(t *testing.T) { | ||
t.Helper() | ||
|
||
tests := []struct { | ||
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec | ||
name string | ||
wantErrors []string | ||
}{ | ||
{ | ||
name: "Validate gateway.networking.k8s.io TargetRef Group is allowed", | ||
wantErrors: []string{"TargetRef Group must be gateway.networking.k8s.io."}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would there be errors in a validTargetRefGroup There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point, I didn't notice I had that there. I'll update that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How did this pass in the pipeline? is it actually running? I'm assuming not since our unit tests only run on the internal and cmd directories. Our There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, nice catch @sjberman ! |
||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "Gateway", | ||
Group: "gateway.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a ClientSettingsPolicy with the targetRef from the test case. | ||
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-policy", | ||
Namespace: "default", | ||
}, | ||
Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: tt.policySpec.TargetRef, | ||
}, | ||
} | ||
validateClientSettingsPolicy(t, clientSettingsPolicy, tt.wantErrors) | ||
}) | ||
} | ||
} | ||
|
||
func testInvalidTargetRefGroup(t *testing.T) { | ||
t.Helper() | ||
|
||
tests := []struct { | ||
policySpec ngfAPIv1alpha1.ClientSettingsPolicySpec | ||
name string | ||
wantErrors []string | ||
}{ | ||
{ | ||
name: "Validate invalid.networking.k8s.io TargetRef Group is not allowed", | ||
wantErrors: []string{"TargetRef Group must be gateway.networking.k8s.io."}, | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "Gateway", | ||
Group: "invalid.networking.k8s.io", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "Validate discovery.k8s.io/v1 TargetRef Group is not allowed", | ||
wantErrors: []string{"TargetRef Group must be gateway.networking.k8s.io."}, | ||
policySpec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: gatewayv1alpha2.LocalPolicyTargetReference{ | ||
Kind: "Gateway", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can make kind There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we do have the various resource Kinds as constants in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: on that note, we can use a different kind for one of these tests, just to try all combinations for invalid targetRef group There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @salonichf5 @sjberman great suggestions here. I'll be sure to make those updates! |
||
Group: "discovery.k8s.io/v1", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a ClientSettingsPolicy with the targetRef from the test case. | ||
clientSettingsPolicy := &ngfAPIv1alpha1.ClientSettingsPolicy{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-policy", | ||
Namespace: "default", | ||
}, | ||
Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ | ||
TargetRef: tt.policySpec.TargetRef, | ||
}, | ||
} | ||
validateClientSettingsPolicy(t, clientSettingsPolicy, tt.wantErrors) | ||
}) | ||
} | ||
} | ||
|
||
func validateClientSettingsPolicy(t *testing.T, | ||
clientSettingsPolicy *ngfAPIv1alpha1.ClientSettingsPolicy, | ||
wantErrors []string, | ||
) { | ||
t.Helper() | ||
sjberman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Register API types with the runtime scheme | ||
// This is necessary to create a fake client that can handle the custom resource. | ||
scheme := runtime.NewScheme() | ||
_ = ngfAPIv1alpha1.AddToScheme(scheme) | ||
|
||
// Create a fake client with the scheme | ||
// This is used to simulate interactions with the Kubernetes API. | ||
k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build() | ||
|
||
err := k8sClient.Create(context.Background(), clientSettingsPolicy) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validation doesn't really make sense to me, when you're creating the fake how does the validation happening? I don't see how you've connected any validation here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main validation lies here: if err != nil {
for _, wantError := range wantErrors {
if !strings.Contains(err.Error(), wantError) {
t.Errorf("missing expected error %q in %v", wantError, err)
}
}
} This loops over the errors that we are expecting and checks if the error we get back from the k8s API from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah but how does the k8sClient throw any errors since its a fake |
||
if err != nil { | ||
t.Logf("Error creating ClientSettingsPolicy %q: %v", | ||
fmt.Sprintf("%v/%v", clientSettingsPolicy.Namespace, clientSettingsPolicy.Name), err) | ||
} | ||
|
||
if err != nil { | ||
for _, wantError := range wantErrors { | ||
if !strings.Contains(err.Error(), wantError) { | ||
t.Errorf("missing expected error %q in %v", wantError, err) | ||
} | ||
} | ||
} | ||
Comment on lines
+237
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We typically use the gomega library to do assertion matching. See our other unit tests for examples, but you can basically do:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to know, thanks @sjberman ! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ go 1.24.2 | |
replace github.com/nginx/nginx-gateway-fabric => ../ | ||
|
||
require ( | ||
github.com/nginx/nginx-gateway-fabric v0.0.0 | ||
github.com/nginx/nginx-gateway-fabric v1.6.2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should stay as-is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @sjberman There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Functionally it may not matter since we perform the |
||
github.com/onsi/ginkgo/v2 v2.23.4 | ||
github.com/onsi/gomega v1.37.0 | ||
github.com/prometheus/client_golang v1.22.0 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can keep the gateway API import with the other 3rd party imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I remember, this affected the linter. I will double check that though.