Skip to content

Commit d36f149

Browse files
committed
test: add tests for TargetCustomizationMode and AllMatches
- bundlereader: verify targetCustomizationMode is propagated from fleet.yaml into bundle.Spec.TargetCustomizationMode - matcher: direct unit tests for hasMatchingRestriction edge cases - integration: AllMatches end-to-end test verifying that all matching customizations are merged into the final BundleDeployment options
1 parent 8869d02 commit d36f149

3 files changed

Lines changed: 184 additions & 0 deletions

File tree

integrationtests/controller/bundle/bundle_targets_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,73 @@ var _ = Describe("Bundle targets", Ordered, func() {
588588
}
589589
})
590590
})
591+
592+
// AllMatches mode: every matching targetCustomization is applied in order,
593+
// so a cluster that matches two customizations receives the merged options
594+
// from both, whereas FirstMatch would stop at the first.
595+
When("TargetCustomizationMode is AllMatches and two customizations match cluster one", func() {
596+
BeforeEach(func() {
597+
bundleName = "all-matches-mode"
598+
bdLabels = map[string]string{
599+
"fleet.cattle.io/bundle-name": bundleName,
600+
"fleet.cattle.io/bundle-namespace": namespace,
601+
}
602+
expectedNumberOfBundleDeployments = 3
603+
604+
// Customization 1: only cluster group "one" → sets region
605+
// Customization 2: all clusters → sets env
606+
// GitRepo target: all clusters (no extra values)
607+
targetsInGitRepo := []v1alpha1.BundleTarget{
608+
{ClusterGroup: "all"},
609+
}
610+
targets = []v1alpha1.BundleTarget{
611+
{
612+
BundleDeploymentOptions: v1alpha1.BundleDeploymentOptions{
613+
Helm: &v1alpha1.HelmOptions{
614+
Values: &v1alpha1.GenericMap{Data: map[string]interface{}{"region": "us-west"}},
615+
},
616+
},
617+
ClusterGroup: "one",
618+
},
619+
{
620+
BundleDeploymentOptions: v1alpha1.BundleDeploymentOptions{
621+
Helm: &v1alpha1.HelmOptions{
622+
Values: &v1alpha1.GenericMap{Data: map[string]interface{}{"env": "prod"}},
623+
},
624+
},
625+
ClusterGroup: "all",
626+
},
627+
}
628+
targetRestrictions = make([]v1alpha1.BundleTarget, len(targetsInGitRepo))
629+
copy(targetRestrictions, targetsInGitRepo)
630+
targets = append(targets, targetsInGitRepo...)
631+
})
632+
633+
JustBeforeEach(func() {
634+
// The outer JustBeforeEach already created the bundle with FirstMatch
635+
// (the default). Patch it to AllMatches so the reconciler re-evaluates.
636+
mod := bundle.DeepCopy()
637+
mod.Spec.TargetCustomizationMode = v1alpha1.TargetCustomizationModeAllMatches
638+
Expect(k8sClient.Patch(ctx, mod, client.MergeFrom(bundle))).ToNot(HaveOccurred())
639+
bundle = mod
640+
})
641+
642+
It("merges all matching customizations into cluster one's BundleDeployment", func() {
643+
bdList := verifyBundlesDeploymentsAreCreated(expectedNumberOfBundleDeployments, bdLabels, bundleName)
644+
for _, bd := range bdList.Items {
645+
values, _ := loadValues(bd)
646+
if strings.Contains(bd.Namespace, "cluster-one") {
647+
// cluster "one" matches both customizations: both keys must be present
648+
Expect(values).To(HaveKeyWithValue("region", "us-west"), "cluster-one should have region from cust1")
649+
Expect(values).To(HaveKeyWithValue("env", "prod"), "cluster-one should have env from cust2")
650+
} else {
651+
// other clusters match only cust2
652+
Expect(values).ToNot(HaveKey("region"), "non-one clusters should not have region")
653+
Expect(values).To(HaveKeyWithValue("env", "prod"), "non-one clusters should have env from cust2")
654+
}
655+
}
656+
})
657+
})
591658
})
592659

593660
func verifyBundlesDeploymentsAreCreated(numBundleDeployments int, bdLabels map[string]string, bundleName string) *v1alpha1.BundleDeploymentList {

internal/bundlereader/read_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package bundlereader
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
11+
)
12+
13+
// TestBundleFromDir_TargetCustomizationModePropagate verifies that the
14+
// targetCustomizationMode field from fleet.yaml is copied into
15+
// bundle.Spec.TargetCustomizationMode by bundleFromDir.
16+
func TestBundleFromDir_TargetCustomizationModePropagate(t *testing.T) {
17+
dir := t.TempDir()
18+
19+
tests := []struct {
20+
name string
21+
yaml string
22+
wantMode fleet.TargetCustomizationMode
23+
}{
24+
{
25+
name: "AllMatches is propagated to BundleSpec",
26+
yaml: `targetCustomizationMode: AllMatches`,
27+
wantMode: fleet.TargetCustomizationModeAllMatches,
28+
},
29+
{
30+
name: "FirstMatch is propagated to BundleSpec",
31+
yaml: `targetCustomizationMode: FirstMatch`,
32+
wantMode: fleet.TargetCustomizationModeFirstMatch,
33+
},
34+
{
35+
name: "omitted mode propagates as empty string (controller uses default)",
36+
yaml: `namespace: test`,
37+
wantMode: "",
38+
},
39+
}
40+
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
bundle, _, err := bundleFromDir(context.Background(), "test", dir, []byte(tt.yaml), nil)
44+
require.NoError(t, err)
45+
assert.Equal(t, tt.wantMode, bundle.Spec.TargetCustomizationMode)
46+
})
47+
}
48+
}

internal/cmd/controller/target/matcher/bundlematch_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,75 @@ import (
1010
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
1111
)
1212

13+
// TestHasMatchingRestriction verifies that the helper correctly distinguishes
14+
// GitRepo targets (which have a matching restriction) from fleet.yaml
15+
// targetCustomizations (which do not).
16+
func TestHasMatchingRestriction(t *testing.T) {
17+
sel := func(m map[string]string) *metav1.LabelSelector {
18+
return &metav1.LabelSelector{MatchLabels: m}
19+
}
20+
21+
tests := []struct {
22+
name string
23+
target fleet.BundleTarget
24+
restrictions []fleet.BundleTargetRestriction
25+
want bool
26+
}{
27+
{
28+
name: "exact name match",
29+
target: fleet.BundleTarget{Name: "foo"},
30+
restrictions: []fleet.BundleTargetRestriction{{Name: "foo"}},
31+
want: true,
32+
},
33+
{
34+
name: "name matches but ClusterSelector differs — not a restriction match",
35+
target: fleet.BundleTarget{Name: "foo", ClusterSelector: sel(map[string]string{"a": "1"})},
36+
restrictions: []fleet.BundleTargetRestriction{
37+
{Name: "foo", ClusterSelector: sel(map[string]string{"a": "2"})},
38+
},
39+
want: false,
40+
},
41+
{
42+
name: "empty restriction list — always a customization",
43+
target: fleet.BundleTarget{Name: "foo"},
44+
restrictions: nil,
45+
want: false,
46+
},
47+
{
48+
name: "name matches but ClusterGroup differs",
49+
target: fleet.BundleTarget{Name: "foo", ClusterGroup: "one"},
50+
restrictions: []fleet.BundleTargetRestriction{
51+
{Name: "foo", ClusterGroup: "two"},
52+
},
53+
want: false,
54+
},
55+
{
56+
name: "ClusterName match without name field",
57+
target: fleet.BundleTarget{ClusterName: "local"},
58+
restrictions: []fleet.BundleTargetRestriction{
59+
{ClusterName: "local"},
60+
},
61+
want: true,
62+
},
63+
{
64+
name: "second restriction matches",
65+
target: fleet.BundleTarget{Name: "bar"},
66+
restrictions: []fleet.BundleTargetRestriction{
67+
{Name: "foo"},
68+
{Name: "bar"},
69+
},
70+
want: true,
71+
},
72+
}
73+
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
got := hasMatchingRestriction(tt.target, tt.restrictions)
77+
assert.Equal(t, tt.want, got)
78+
})
79+
}
80+
}
81+
1382
// makeBundle builds a minimal Bundle reflecting the order produced by bundlereader:
1483
// targetCustomizations come first in Targets, followed by the GitRepo target.
1584
// The GitRepo target is also added as a TargetRestriction.

0 commit comments

Comments
 (0)