Testing: Add missing unit tests for pkg/itemblock

Add comprehensive unit tests for pkg/itemblock which previously had
zero test coverage. The new tests cover AddUnstructured and FindItem
methods with 14 test cases across 3 test functions, including preferred
GVR ordering, nil item handling, namespace/name filtering, unparseable
apiVersion handling, and cluster-scoped resource matching.

Fixes #10418

Signed-off-by: opbot_xd <awasthikrishna23052005@gmail.com>
This commit is contained in:
opbot_xd
2026-08-27 05:43:05 +05:30
parent d9c25173f7
commit e67e08ce2f
2 changed files with 358 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
Add unit tests for pkg/itemblock
+357
View File
@@ -0,0 +1,357 @@
/*
Copyright the Velero contributors.
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.
*/
package itemblock
import (
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// newUnstructuredItem creates an unstructured object with the given apiVersion,
// kind, namespace, and name for use in tests.
func newUnstructuredItem(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]any{
"namespace": namespace,
"name": name,
},
},
}
}
func TestAddUnstructured(t *testing.T) {
tests := []struct {
name string
items []ItemBlockItem
expectedLen int
}{
{
name: "add single item to empty block",
items: []ItemBlockItem{
{
Gr: schema.GroupResource{Group: "apps", Resource: "deployments"},
Item: newUnstructuredItem("apps/v1", "Deployment", "default", "nginx"),
PreferredGVR: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
},
},
expectedLen: 1,
},
{
name: "add multiple items",
items: []ItemBlockItem{
{
Gr: schema.GroupResource{Group: "", Resource: "pods"},
Item: newUnstructuredItem("v1", "Pod", "default", "pod-1"),
PreferredGVR: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
},
{
Gr: schema.GroupResource{Group: "", Resource: "services"},
Item: newUnstructuredItem("v1", "Service", "kube-system", "kube-dns"),
PreferredGVR: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"},
},
{
Gr: schema.GroupResource{Group: "apps", Resource: "deployments"},
Item: newUnstructuredItem("apps/v1", "Deployment", "default", "web"),
PreferredGVR: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
},
},
expectedLen: 3,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ib := &ItemBlock{
Log: logrus.New(),
}
for _, item := range tc.items {
ib.AddUnstructured(item.Gr, item.Item, item.PreferredGVR)
}
require.Len(t, ib.Items, tc.expectedLen)
// Verify each added item matches what was provided
for i, item := range tc.items {
assert.Equal(t, item.Gr, ib.Items[i].Gr)
assert.Equal(t, item.Item, ib.Items[i].Item)
assert.Equal(t, item.PreferredGVR, ib.Items[i].PreferredGVR)
}
})
}
}
func TestFindItem(t *testing.T) {
podsGR := schema.GroupResource{Group: "", Resource: "pods"}
deploymentsGR := schema.GroupResource{Group: "apps", Resource: "deployments"}
servicesGR := schema.GroupResource{Group: "", Resource: "services"}
podsGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
deploymentsGVR := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
tests := []struct {
name string
existingItems []ItemBlockItem
searchGR schema.GroupResource
searchNamespace string
searchName string
expectedCount int
// If set, verifies the names in the returned items are in this exact order
expectedNames []string
}{
{
name: "find item in empty block returns nil",
existingItems: nil,
searchGR: podsGR,
searchNamespace: "default",
searchName: "pod-1",
expectedCount: 0,
},
{
name: "find matching item by GR, namespace, and name",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: newUnstructuredItem("v1", "Pod", "default", "pod-1"),
PreferredGVR: podsGVR,
},
},
searchGR: podsGR,
searchNamespace: "default",
searchName: "pod-1",
expectedCount: 1,
expectedNames: []string{"pod-1"},
},
{
name: "no match when GR differs",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: newUnstructuredItem("v1", "Pod", "default", "pod-1"),
PreferredGVR: podsGVR,
},
},
searchGR: deploymentsGR,
searchNamespace: "default",
searchName: "pod-1",
expectedCount: 0,
},
{
name: "no match when namespace differs",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: newUnstructuredItem("v1", "Pod", "default", "pod-1"),
PreferredGVR: podsGVR,
},
},
searchGR: podsGR,
searchNamespace: "kube-system",
searchName: "pod-1",
expectedCount: 0,
},
{
name: "no match when name differs",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: newUnstructuredItem("v1", "Pod", "default", "pod-1"),
PreferredGVR: podsGVR,
},
},
searchGR: podsGR,
searchNamespace: "default",
searchName: "pod-2",
expectedCount: 0,
},
{
name: "nil item is skipped",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: nil,
PreferredGVR: podsGVR,
},
},
searchGR: podsGR,
searchNamespace: "default",
searchName: "pod-1",
expectedCount: 0,
},
{
name: "preferred GVR match is returned first",
existingItems: []ItemBlockItem{
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1beta1", "Deployment", "default", "web"),
PreferredGVR: deploymentsGVR, // preferred is v1, item is v1beta1 → non-preferred
},
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1", "Deployment", "default", "web"),
PreferredGVR: deploymentsGVR, // preferred is v1, item is v1 → preferred match
},
},
searchGR: deploymentsGR,
searchNamespace: "default",
searchName: "web",
expectedCount: 2,
expectedNames: []string{"web", "web"},
},
{
name: "multiple non-preferred items returned when no preferred match",
existingItems: []ItemBlockItem{
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1beta1", "Deployment", "default", "web"),
PreferredGVR: deploymentsGVR,
},
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1beta2", "Deployment", "default", "web"),
PreferredGVR: deploymentsGVR,
},
},
searchGR: deploymentsGR,
searchNamespace: "default",
searchName: "web",
expectedCount: 2,
},
{
name: "item with unparsable apiVersion is treated as non-preferred",
existingItems: []ItemBlockItem{
{
Gr: deploymentsGR,
Item: newUnstructuredItem("not/a/valid/version", "Deployment", "default", "web"),
PreferredGVR: deploymentsGVR,
},
},
searchGR: deploymentsGR,
searchNamespace: "default",
searchName: "web",
expectedCount: 1,
},
{
name: "only matching GR items are returned from mixed block",
existingItems: []ItemBlockItem{
{
Gr: podsGR,
Item: newUnstructuredItem("v1", "Pod", "default", "app"),
PreferredGVR: podsGVR,
},
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1", "Deployment", "default", "app"),
PreferredGVR: deploymentsGVR,
},
{
Gr: servicesGR,
Item: newUnstructuredItem("v1", "Service", "default", "app"),
PreferredGVR: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"},
},
},
searchGR: deploymentsGR,
searchNamespace: "default",
searchName: "app",
expectedCount: 1,
expectedNames: []string{"app"},
},
{
name: "cluster-scoped item found with empty namespace",
existingItems: []ItemBlockItem{
{
Gr: schema.GroupResource{Group: "", Resource: "namespaces"},
Item: newUnstructuredItem("v1", "Namespace", "", "production"),
PreferredGVR: schema.GroupVersionResource{
Group: "", Version: "v1", Resource: "namespaces",
},
},
},
searchGR: schema.GroupResource{Group: "", Resource: "namespaces"},
searchNamespace: "",
searchName: "production",
expectedCount: 1,
expectedNames: []string{"production"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ib := &ItemBlock{
Log: logrus.New(),
Items: tc.existingItems,
}
result := ib.FindItem(tc.searchGR, tc.searchNamespace, tc.searchName)
require.Len(t, result, tc.expectedCount)
if tc.expectedNames != nil {
for i, expectedName := range tc.expectedNames {
assert.Equal(t, expectedName, result[i].Item.GetName())
}
}
})
}
}
func TestFindItemPreferredOrdering(t *testing.T) {
deploymentsGR := schema.GroupResource{Group: "apps", Resource: "deployments"}
deploymentsGVR := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
// Insert non-preferred first, preferred second to verify ordering
ib := &ItemBlock{
Log: logrus.New(),
Items: []ItemBlockItem{
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1beta1", "Deployment", "ns", "deploy"),
PreferredGVR: deploymentsGVR,
},
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1", "Deployment", "ns", "deploy"),
PreferredGVR: deploymentsGVR,
},
{
Gr: deploymentsGR,
Item: newUnstructuredItem("apps/v1beta2", "Deployment", "ns", "deploy"),
PreferredGVR: deploymentsGVR,
},
},
}
result := ib.FindItem(deploymentsGR, "ns", "deploy")
require.Len(t, result, 3)
// The preferred match (apps/v1) should be first, regardless of insertion order
assert.Equal(t, "apps/v1", result[0].Item.GetAPIVersion(),
"preferred GVR match should be returned first")
// Non-preferred items follow in insertion order
assert.Equal(t, "apps/v1beta1", result[1].Item.GetAPIVersion())
assert.Equal(t, "apps/v1beta2", result[2].Item.GetAPIVersion())
}