Skip to content

Commit 0107e6b

Browse files
committed
add unit tests for helpers and simplify collectOrderedGateways
1 parent d9b51c4 commit 0107e6b

File tree

4 files changed

+245
-18
lines changed

4 files changed

+245
-18
lines changed

internal/controller/state/graph/backend_tls_policy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ func TestValidateBackendTLSPolicy(t *testing.T) {
402402
},
403403
},
404404
{
405-
name: "valid case with many ancestors (ancestor limit now handled during gateway assignment)",
405+
name: "valid case with many ancestors",
406406
tlsPolicy: &v1alpha3.BackendTLSPolicy{
407407
ObjectMeta: metav1.ObjectMeta{
408408
Name: "tls-policy",

internal/controller/state/graph/policies.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ func collectOrderedGatewaysForService(
9898
svc *ReferencedService,
9999
gateways map[types.NamespacedName]*Gateway,
100100
existingNGFGatewayAncestors map[types.NamespacedName]struct{},
101-
) (existingGateways []types.NamespacedName, newGateways []types.NamespacedName) {
102-
existingGateways = make([]types.NamespacedName, 0, len(svc.GatewayNsNames))
103-
newGateways = make([]types.NamespacedName, 0, len(svc.GatewayNsNames))
101+
) []types.NamespacedName {
102+
existingGateways := make([]types.NamespacedName, 0, len(svc.GatewayNsNames))
103+
newGateways := make([]types.NamespacedName, 0, len(svc.GatewayNsNames))
104104

105105
for gwNsName := range svc.GatewayNsNames {
106106
if _, exists := existingNGFGatewayAncestors[gwNsName]; exists {
@@ -113,7 +113,8 @@ func collectOrderedGatewaysForService(
113113
sortGatewaysByCreationTime(existingGateways, gateways)
114114
sortGatewaysByCreationTime(newGateways, gateways)
115115

116-
return existingGateways, newGateways
116+
existingGateways = append(existingGateways, newGateways...)
117+
return existingGateways
117118
}
118119

119120
func (g *Graph) attachPolicies(validator validation.PolicyValidator, ctlrName string, logger logr.Logger) {
@@ -158,11 +159,7 @@ func attachPolicyToService(
158159
existingNGFGatewayAncestors := extractExistingNGFGatewayAncestorsForPolicy(policy, ctlrName)
159160

160161
// Collect and order gateways with existing gateway prioritization
161-
existingGateways, newGateways := collectOrderedGatewaysForService(
162-
svc, gws, existingNGFGatewayAncestors)
163-
164-
existingGateways = append(existingGateways, newGateways...)
165-
orderedGateways := existingGateways
162+
orderedGateways := collectOrderedGatewaysForService(svc, gws, existingNGFGatewayAncestors)
166163

167164
for _, gwNsName := range orderedGateways {
168165
gw := gws[gwNsName]

internal/controller/state/graph/policy_ancestor.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,7 @@ func parentRefEqual(ref1, ref2 v1.ParentReference) bool {
8383
return true
8484
}
8585

86-
// Helper functions to eliminate code duplication
87-
88-
// getAncestorName generates a human-readable name for an ancestor from a ParentReference.
89-
// Returns namespace/name format if namespace is specified, otherwise just name.
86+
// getAncestorName returns namespace/name format if namespace is specified, otherwise just name.
9087
func getAncestorName(ancestorRef v1.ParentReference) string {
9188
ancestorName := string(ancestorRef.Name)
9289
if ancestorRef.Namespace != nil {
@@ -95,15 +92,14 @@ func getAncestorName(ancestorRef v1.ParentReference) string {
9592
return ancestorName
9693
}
9794

98-
// getPolicyName generates a human-readable name for a policy in namespace/name format.
95+
// getPolicyName returns a human-readable name for a policy in namespace/name format.
9996
func getPolicyName(policy policies.Policy) string {
10097
return policy.GetNamespace() + "/" + policy.GetName()
10198
}
10299

103-
// getPolicyKind returns the policy kind with defensive programming for test environments.
104-
// Returns "Policy" as fallback if GetObjectKind() returns nil.
100+
// getPolicyKind returns the policy kind or "Policy" if GetObjectKind() returns nil.
105101
func getPolicyKind(policy policies.Policy) string {
106-
policyKind := "Policy" // Default fallback
102+
policyKind := "Policy"
107103
if objKind := policy.GetObjectKind(); objKind != nil {
108104
policyKind = objKind.GroupVersionKind().Kind
109105
}

internal/controller/state/graph/policy_ancestor_test.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ package graph
22

33
import (
44
"testing"
5+
"time"
56

7+
"github.com/go-logr/logr/testr"
68
. "github.com/onsi/gomega"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/runtime/schema"
712
"k8s.io/apimachinery/pkg/types"
813
v1 "sigs.k8s.io/gateway-api/apis/v1"
914
"sigs.k8s.io/gateway-api/apis/v1alpha2"
1015

1116
ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha2"
17+
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/nginx/config/policies"
1218
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
1319
)
1420

@@ -232,3 +238,231 @@ func TestParentRefEqual(t *testing.T) {
232238
})
233239
}
234240
}
241+
242+
func TestLogAncestorLimitReached(t *testing.T) {
243+
t.Parallel()
244+
logger := testr.New(t)
245+
logAncestorLimitReached(logger, "test-policy", "TestPolicy", "test-ancestor")
246+
}
247+
248+
func TestGetAncestorName(t *testing.T) {
249+
t.Parallel()
250+
tests := []struct {
251+
name string
252+
ref v1.ParentReference
253+
expected string
254+
}{
255+
{
256+
name: "with namespace",
257+
ref: v1.ParentReference{
258+
Name: "test-gw",
259+
Namespace: func() *v1.Namespace { ns := v1.Namespace("test-ns"); return &ns }(),
260+
},
261+
expected: "test-ns/test-gw",
262+
},
263+
{
264+
name: "without namespace",
265+
ref: v1.ParentReference{
266+
Name: "test-gw",
267+
},
268+
expected: "test-gw",
269+
},
270+
}
271+
272+
for _, test := range tests {
273+
t.Run(test.name, func(t *testing.T) {
274+
t.Parallel()
275+
g := NewWithT(t)
276+
g.Expect(getAncestorName(test.ref)).To(Equal(test.expected))
277+
})
278+
}
279+
}
280+
281+
type mockPolicy struct {
282+
name string
283+
namespace string
284+
kind string
285+
}
286+
287+
func (m *mockPolicy) GetName() string { return m.name }
288+
func (m *mockPolicy) SetName(name string) { m.name = name }
289+
func (m *mockPolicy) GetNamespace() string { return m.namespace }
290+
func (m *mockPolicy) SetNamespace(namespace string) { m.namespace = namespace }
291+
func (m *mockPolicy) GetObjectKind() schema.ObjectKind {
292+
if m.kind == "" {
293+
return nil
294+
}
295+
return &mockObjectKind{kind: m.kind}
296+
}
297+
func (m *mockPolicy) GetTargetRefs() []v1alpha2.LocalPolicyTargetReference { return nil }
298+
func (m *mockPolicy) GetPolicyStatus() v1alpha2.PolicyStatus { return v1alpha2.PolicyStatus{} }
299+
func (m *mockPolicy) SetPolicyStatus(_ v1alpha2.PolicyStatus) {}
300+
func (m *mockPolicy) DeepCopyObject() runtime.Object { return m }
301+
func (m *mockPolicy) GetUID() types.UID { return "" }
302+
func (m *mockPolicy) GetResourceVersion() string { return "" }
303+
func (m *mockPolicy) SetUID(_ types.UID) {}
304+
func (m *mockPolicy) SetResourceVersion(_ string) {}
305+
func (m *mockPolicy) GetGeneration() int64 { return 0 }
306+
func (m *mockPolicy) SetGeneration(_ int64) {}
307+
func (m *mockPolicy) GetCreationTimestamp() metav1.Time { return metav1.Time{} }
308+
func (m *mockPolicy) SetCreationTimestamp(_ metav1.Time) {}
309+
func (m *mockPolicy) GetDeletionTimestamp() *metav1.Time { return nil }
310+
func (m *mockPolicy) SetDeletionTimestamp(_ *metav1.Time) {}
311+
func (m *mockPolicy) GetDeletionGracePeriodSeconds() *int64 { return nil }
312+
func (m *mockPolicy) SetDeletionGracePeriodSeconds(*int64) {}
313+
func (m *mockPolicy) GetLabels() map[string]string { return nil }
314+
func (m *mockPolicy) SetLabels(_ map[string]string) {}
315+
func (m *mockPolicy) GetAnnotations() map[string]string { return nil }
316+
func (m *mockPolicy) SetAnnotations(_ map[string]string) {}
317+
func (m *mockPolicy) GetFinalizers() []string { return nil }
318+
func (m *mockPolicy) SetFinalizers(_ []string) {}
319+
func (m *mockPolicy) GetOwnerReferences() []metav1.OwnerReference { return nil }
320+
func (m *mockPolicy) SetOwnerReferences([]metav1.OwnerReference) {}
321+
func (m *mockPolicy) GetManagedFields() []metav1.ManagedFieldsEntry { return nil }
322+
func (m *mockPolicy) SetManagedFields(_ []metav1.ManagedFieldsEntry) {}
323+
func (m *mockPolicy) GetGenerateName() string { return "" }
324+
func (m *mockPolicy) SetGenerateName(_ string) {}
325+
func (m *mockPolicy) GetSelfLink() string { return "" }
326+
func (m *mockPolicy) SetSelfLink(_ string) {}
327+
328+
type mockObjectKind struct{ kind string }
329+
330+
func (m *mockObjectKind) GroupVersionKind() schema.GroupVersionKind {
331+
return schema.GroupVersionKind{Kind: m.kind}
332+
}
333+
func (m *mockObjectKind) SetGroupVersionKind(_ schema.GroupVersionKind) {}
334+
335+
func TestGetPolicyName(t *testing.T) {
336+
t.Parallel()
337+
g := NewWithT(t)
338+
policy := &mockPolicy{name: "test-policy", namespace: "test-ns"}
339+
g.Expect(getPolicyName(policy)).To(Equal("test-ns/test-policy"))
340+
}
341+
342+
func TestGetPolicyKind(t *testing.T) {
343+
t.Parallel()
344+
tests := []struct {
345+
name string
346+
policy policies.Policy
347+
expected string
348+
}{
349+
{
350+
name: "with kind",
351+
policy: &mockPolicy{kind: "TestPolicy"},
352+
expected: "TestPolicy",
353+
},
354+
{
355+
name: "without kind",
356+
policy: &mockPolicy{},
357+
expected: "Policy",
358+
},
359+
}
360+
361+
for _, test := range tests {
362+
t.Run(test.name, func(t *testing.T) {
363+
t.Parallel()
364+
g := NewWithT(t)
365+
g.Expect(getPolicyKind(test.policy)).To(Equal(test.expected))
366+
})
367+
}
368+
}
369+
370+
func TestCompareNamespacedNames(t *testing.T) {
371+
t.Parallel()
372+
tests := []struct {
373+
name string
374+
a, b types.NamespacedName
375+
expected bool
376+
}{
377+
{
378+
name: "same namespace, a name < b name",
379+
a: types.NamespacedName{Namespace: "ns", Name: "a"},
380+
b: types.NamespacedName{Namespace: "ns", Name: "b"},
381+
expected: true,
382+
},
383+
{
384+
name: "same namespace, a name > b name",
385+
a: types.NamespacedName{Namespace: "ns", Name: "b"},
386+
b: types.NamespacedName{Namespace: "ns", Name: "a"},
387+
expected: false,
388+
},
389+
{
390+
name: "a namespace < b namespace",
391+
a: types.NamespacedName{Namespace: "a", Name: "z"},
392+
b: types.NamespacedName{Namespace: "b", Name: "a"},
393+
expected: true,
394+
},
395+
{
396+
name: "a namespace > b namespace",
397+
a: types.NamespacedName{Namespace: "b", Name: "a"},
398+
b: types.NamespacedName{Namespace: "a", Name: "z"},
399+
expected: false,
400+
},
401+
}
402+
403+
for _, test := range tests {
404+
t.Run(test.name, func(t *testing.T) {
405+
t.Parallel()
406+
g := NewWithT(t)
407+
g.Expect(compareNamespacedNames(test.a, test.b)).To(Equal(test.expected))
408+
})
409+
}
410+
}
411+
412+
func TestSortGatewaysByCreationTime(t *testing.T) {
413+
t.Parallel()
414+
now := time.Now()
415+
gw1Name := types.NamespacedName{Namespace: "test", Name: "gw1"}
416+
gw2Name := types.NamespacedName{Namespace: "test", Name: "gw2"}
417+
418+
tests := []struct {
419+
name string
420+
gateways map[types.NamespacedName]*Gateway
421+
names []types.NamespacedName
422+
expected []types.NamespacedName
423+
}{
424+
{
425+
name: "sort by creation time",
426+
gateways: map[types.NamespacedName]*Gateway{
427+
gw1Name: {Source: &v1.Gateway{
428+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now.Add(time.Hour))},
429+
}},
430+
gw2Name: {Source: &v1.Gateway{
431+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now)},
432+
}},
433+
},
434+
names: []types.NamespacedName{gw1Name, gw2Name},
435+
expected: []types.NamespacedName{gw2Name, gw1Name},
436+
},
437+
{
438+
name: "same creation time, sort by namespace/name",
439+
gateways: map[types.NamespacedName]*Gateway{
440+
gw2Name: {Source: &v1.Gateway{
441+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now)},
442+
}},
443+
gw1Name: {Source: &v1.Gateway{
444+
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.NewTime(now)},
445+
}},
446+
},
447+
names: []types.NamespacedName{gw2Name, gw1Name},
448+
expected: []types.NamespacedName{gw1Name, gw2Name},
449+
},
450+
{
451+
name: "nil gateway fallback to namespace/name",
452+
gateways: map[types.NamespacedName]*Gateway{gw1Name: nil},
453+
names: []types.NamespacedName{gw2Name, gw1Name},
454+
expected: []types.NamespacedName{gw1Name, gw2Name},
455+
},
456+
}
457+
458+
for _, test := range tests {
459+
t.Run(test.name, func(t *testing.T) {
460+
t.Parallel()
461+
g := NewWithT(t)
462+
names := make([]types.NamespacedName, len(test.names))
463+
copy(names, test.names)
464+
sortGatewaysByCreationTime(names, test.gateways)
465+
g.Expect(names).To(Equal(test.expected))
466+
})
467+
}
468+
}

0 commit comments

Comments
 (0)