@@ -1255,6 +1255,312 @@ func TestIterateHierarchyV2_CrossNamespaceOwnerReference(t *testing.T) {
12551255 assert .True (t , foundNamespacedChild , "Should visit Deployment child (this tests the fix)" )
12561256}
12571257
1258+ // TestBuildGraph_NilAllResources tests buildGraph with nil allResources parameter
1259+ func TestBuildGraph_NilAllResources (t * testing.T ) {
1260+ // Create test resources
1261+ parentUID := types .UID ("parent-123" )
1262+ childUID := types .UID ("child-456" )
1263+
1264+ parent := & Resource {
1265+ Ref : corev1.ObjectReference {
1266+ APIVersion : "v1" ,
1267+ Kind : "ConfigMap" ,
1268+ Name : "parent" ,
1269+ Namespace : "test-ns" ,
1270+ UID : parentUID ,
1271+ },
1272+ }
1273+
1274+ child := & Resource {
1275+ Ref : corev1.ObjectReference {
1276+ APIVersion : "v1" ,
1277+ Kind : "Pod" ,
1278+ Name : "child" ,
1279+ Namespace : "test-ns" ,
1280+ UID : childUID ,
1281+ },
1282+ OwnerRefs : []metav1.OwnerReference {{
1283+ APIVersion : "v1" ,
1284+ Kind : "ConfigMap" ,
1285+ Name : "parent" ,
1286+ UID : parentUID ,
1287+ }},
1288+ }
1289+
1290+ nsNodes := map [kube.ResourceKey ]* Resource {
1291+ parent .ResourceKey (): parent ,
1292+ child .ResourceKey (): child ,
1293+ }
1294+
1295+ // Call buildGraph with nil allResources
1296+ graph := buildGraph (nsNodes , nil )
1297+
1298+ // Should still work for same-namespace relationships
1299+ assert .Contains (t , graph , parent .ResourceKey ())
1300+ assert .Contains (t , graph [parent .ResourceKey ()], childUID )
1301+ }
1302+
1303+ // TestBuildGraph_InvalidAPIVersion tests handling of invalid API versions in owner references
1304+ func TestBuildGraph_InvalidAPIVersion (t * testing.T ) {
1305+ childUID := types .UID ("child-123" )
1306+
1307+ child := & Resource {
1308+ Ref : corev1.ObjectReference {
1309+ APIVersion : "v1" ,
1310+ Kind : "Pod" ,
1311+ Name : "child" ,
1312+ Namespace : "test-ns" ,
1313+ UID : childUID ,
1314+ },
1315+ OwnerRefs : []metav1.OwnerReference {{
1316+ APIVersion : "invalid/api/version" , // Invalid API version
1317+ Kind : "ConfigMap" ,
1318+ Name : "parent" ,
1319+ // No UID - should trigger API version parsing
1320+ }},
1321+ }
1322+
1323+ nsNodes := map [kube.ResourceKey ]* Resource {
1324+ child .ResourceKey (): child ,
1325+ }
1326+
1327+ allResources := map [kube.ResourceKey ]* Resource {
1328+ child .ResourceKey (): child ,
1329+ }
1330+
1331+ // Should not panic and should handle invalid API version gracefully
1332+ graph := buildGraph (nsNodes , allResources )
1333+
1334+ // No parent should be found due to invalid API version
1335+ assert .Empty (t , graph )
1336+ }
1337+
1338+ // TestBuildGraph_CrossNamespaceMissingUID tests cross-namespace parent resolution with missing UID
1339+ func TestBuildGraph_CrossNamespaceMissingUID (t * testing.T ) {
1340+ parentUID := types .UID ("parent-123" )
1341+ childUID := types .UID ("child-456" )
1342+
1343+ // Cluster-scoped parent
1344+ parent := & Resource {
1345+ Ref : corev1.ObjectReference {
1346+ APIVersion : "v1" ,
1347+ Kind : "ConfigMap" ,
1348+ Name : "parent" ,
1349+ Namespace : "" , // Cluster-scoped
1350+ UID : parentUID ,
1351+ },
1352+ }
1353+
1354+ // Namespaced child with owner reference missing UID
1355+ child := & Resource {
1356+ Ref : corev1.ObjectReference {
1357+ APIVersion : "v1" ,
1358+ Kind : "Pod" ,
1359+ Name : "child" ,
1360+ Namespace : "test-ns" ,
1361+ UID : childUID ,
1362+ },
1363+ OwnerRefs : []metav1.OwnerReference {{
1364+ APIVersion : "v1" ,
1365+ Kind : "ConfigMap" ,
1366+ Name : "parent" ,
1367+ // UID is missing - should be resolved via cross-namespace lookup
1368+ }},
1369+ }
1370+
1371+ nsNodes := map [kube.ResourceKey ]* Resource {
1372+ child .ResourceKey (): child ,
1373+ }
1374+
1375+ allResources := map [kube.ResourceKey ]* Resource {
1376+ parent .ResourceKey (): parent ,
1377+ child .ResourceKey (): child ,
1378+ }
1379+
1380+ graph := buildGraph (nsNodes , allResources )
1381+
1382+ // Should find the parent via cross-namespace lookup and establish relationship
1383+ assert .Contains (t , graph , parent .ResourceKey ())
1384+ assert .Contains (t , graph [parent .ResourceKey ()], childUID )
1385+
1386+ // Verify UID was backfilled
1387+ assert .Equal (t , parentUID , child .OwnerRefs [0 ].UID )
1388+ }
1389+
1390+ // TestBuildGraph_NonExistentParent tests cross-namespace child with non-existent parent
1391+ func TestBuildGraph_NonExistentParent (t * testing.T ) {
1392+ childUID := types .UID ("child-456" )
1393+
1394+ child := & Resource {
1395+ Ref : corev1.ObjectReference {
1396+ APIVersion : "v1" ,
1397+ Kind : "Pod" ,
1398+ Name : "child" ,
1399+ Namespace : "test-ns" ,
1400+ UID : childUID ,
1401+ },
1402+ OwnerRefs : []metav1.OwnerReference {{
1403+ APIVersion : "v1" ,
1404+ Kind : "ConfigMap" ,
1405+ Name : "non-existent-parent" ,
1406+ // No UID - should trigger lookup that fails
1407+ }},
1408+ }
1409+
1410+ nsNodes := map [kube.ResourceKey ]* Resource {
1411+ child .ResourceKey (): child ,
1412+ }
1413+
1414+ allResources := map [kube.ResourceKey ]* Resource {
1415+ child .ResourceKey (): child ,
1416+ // Parent is not in allResources
1417+ }
1418+
1419+ graph := buildGraph (nsNodes , allResources )
1420+
1421+ // No relationships should be established
1422+ assert .Empty (t , graph )
1423+ }
1424+
1425+ // TestBuildGraph_CrossNamespaceUIDLookup tests cross-namespace parent lookup by UID
1426+ func TestBuildGraph_CrossNamespaceUIDLookup (t * testing.T ) {
1427+ parentUID := types .UID ("parent-123" )
1428+ childUID := types .UID ("child-456" )
1429+
1430+ // Cluster-scoped parent
1431+ parent := & Resource {
1432+ Ref : corev1.ObjectReference {
1433+ APIVersion : "v1" ,
1434+ Kind : "ConfigMap" ,
1435+ Name : "parent" ,
1436+ Namespace : "" , // Cluster-scoped
1437+ UID : parentUID ,
1438+ },
1439+ }
1440+
1441+ // Namespaced child with owner reference that has UID but parent not in same namespace
1442+ child := & Resource {
1443+ Ref : corev1.ObjectReference {
1444+ APIVersion : "v1" ,
1445+ Kind : "Pod" ,
1446+ Name : "child" ,
1447+ Namespace : "test-ns" ,
1448+ UID : childUID ,
1449+ },
1450+ OwnerRefs : []metav1.OwnerReference {{
1451+ APIVersion : "v1" ,
1452+ Kind : "ConfigMap" ,
1453+ Name : "parent" ,
1454+ UID : parentUID , // UID is present
1455+ }},
1456+ }
1457+
1458+ nsNodes := map [kube.ResourceKey ]* Resource {
1459+ child .ResourceKey (): child ,
1460+ // Parent is not in same namespace
1461+ }
1462+
1463+ allResources := map [kube.ResourceKey ]* Resource {
1464+ parent .ResourceKey (): parent ,
1465+ child .ResourceKey (): child ,
1466+ }
1467+
1468+ graph := buildGraph (nsNodes , allResources )
1469+
1470+ // Should establish cross-namespace relationship via UID lookup
1471+ assert .Contains (t , graph , parent .ResourceKey ())
1472+ assert .Contains (t , graph [parent .ResourceKey ()], childUID )
1473+ }
1474+
1475+ // TestBuildGraph_DuplicateUIDs tests handling of resources with duplicate UIDs
1476+ func TestBuildGraph_DuplicateUIDs (t * testing.T ) {
1477+ parentUID := types .UID ("parent-123" )
1478+ duplicateUID := types .UID ("duplicate-456" )
1479+
1480+ parent := & Resource {
1481+ Ref : corev1.ObjectReference {
1482+ APIVersion : "v1" ,
1483+ Kind : "ConfigMap" ,
1484+ Name : "parent" ,
1485+ Namespace : "test-ns" ,
1486+ UID : parentUID ,
1487+ },
1488+ }
1489+
1490+ // Two children with the same UID (simulating replicasets from different API groups)
1491+ child1 := & Resource {
1492+ Ref : corev1.ObjectReference {
1493+ APIVersion : "apps/v1" ,
1494+ Kind : "ReplicaSet" ,
1495+ Name : "child-apps" ,
1496+ Namespace : "test-ns" ,
1497+ UID : duplicateUID ,
1498+ },
1499+ OwnerRefs : []metav1.OwnerReference {{
1500+ APIVersion : "v1" ,
1501+ Kind : "ConfigMap" ,
1502+ Name : "parent" ,
1503+ UID : parentUID ,
1504+ }},
1505+ }
1506+
1507+ child2 := & Resource {
1508+ Ref : corev1.ObjectReference {
1509+ APIVersion : "extensions/v1beta1" ,
1510+ Kind : "ReplicaSet" ,
1511+ Name : "child-extensions" ,
1512+ Namespace : "test-ns" ,
1513+ UID : duplicateUID ,
1514+ },
1515+ OwnerRefs : []metav1.OwnerReference {{
1516+ APIVersion : "v1" ,
1517+ Kind : "ConfigMap" ,
1518+ Name : "parent" ,
1519+ UID : parentUID ,
1520+ }},
1521+ }
1522+
1523+ nsNodes := map [kube.ResourceKey ]* Resource {
1524+ parent .ResourceKey (): parent ,
1525+ child1 .ResourceKey (): child1 ,
1526+ child2 .ResourceKey (): child2 ,
1527+ }
1528+
1529+ graph := buildGraph (nsNodes , nil )
1530+
1531+ // Should handle duplicate UIDs gracefully by picking consistently
1532+ assert .Contains (t , graph , parent .ResourceKey ())
1533+ assert .Contains (t , graph [parent .ResourceKey ()], duplicateUID )
1534+
1535+ // Should pick the same child consistently (based on string comparison)
1536+ selectedChild := graph [parent .ResourceKey ()][duplicateUID ]
1537+ assert .NotNil (t , selectedChild )
1538+ }
1539+
1540+ // TestIterateHierarchyV2_EdgeCases tests additional edge cases for hierarchy iteration
1541+ func TestIterateHierarchyV2_EdgeCases (t * testing.T ) {
1542+ cluster := newCluster (t )
1543+
1544+ t .Run ("EmptyKeysList" , func (t * testing.T ) {
1545+ var visited []kube.ResourceKey
1546+ cluster .IterateHierarchyV2 ([]kube.ResourceKey {}, func (resource * Resource , _ map [kube.ResourceKey ]* Resource ) bool {
1547+ visited = append (visited , resource .ResourceKey ())
1548+ return true
1549+ })
1550+ assert .Empty (t , visited )
1551+ })
1552+
1553+ t .Run ("NonExistentKeys" , func (t * testing.T ) {
1554+ var visited []kube.ResourceKey
1555+ nonExistentKey := kube.ResourceKey {Group : "fake" , Kind : "Fake" , Namespace : "fake" , Name : "fake" }
1556+ cluster .IterateHierarchyV2 ([]kube.ResourceKey {nonExistentKey }, func (resource * Resource , _ map [kube.ResourceKey ]* Resource ) bool {
1557+ visited = append (visited , resource .ResourceKey ())
1558+ return true
1559+ })
1560+ assert .Empty (t , visited )
1561+ })
1562+ }
1563+
12581564// Test_watchEvents_Deadlock validates that starting watches will not create a deadlock
12591565// caused by using improper locking in various callback methods when there is a high load on the
12601566// system.
0 commit comments