@@ -1044,3 +1044,79 @@ func TestFetchEntityDescendentPaths_NoTerminalsReturnsEmpty(t *testing.T) {
10441044 })
10451045 require .NoError (t , err )
10461046}
1047+
1048+ // TestFetchEntityDescendentPaths_SharedIntermediateNode verifies that two terminal nodes sharing
1049+ // an intermediate container node both produce complete paths reaching root. Each traversal
1050+ // independently reaches root through the shared intermediate without being truncated.
1051+ func TestFetchEntityDescendentPaths_SharedIntermediateNode (t * testing.T ) {
1052+ t .Parallel ()
1053+
1054+ suite := setupIntegrationTestSuite (t )
1055+ defer teardownIntegrationTestSuite (t , & suite )
1056+
1057+ var (
1058+ tenantID = integration .RandomObjectID (t )
1059+ tenantNode = NewAzureTenant (t , & suite , tenantID )
1060+ subscriptionNode = NewNode (t , & suite , graph .AsProperties (graph.PropertyMap {
1061+ common .Name : "SharedSubscription" ,
1062+ common .ObjectID : integration .RandomObjectID (t ),
1063+ graphAzure .TenantID : tenantID ,
1064+ }), graphAzure .Entity , graphAzure .Subscription )
1065+ app1 = NewAzureApplication (t , & suite , "App1" , integration .RandomObjectID (t ), tenantID )
1066+ app2 = NewAzureApplication (t , & suite , "App2" , integration .RandomObjectID (t ), tenantID )
1067+ )
1068+
1069+ // tenant --Contains--> subscription --Contains--> app1
1070+ // --Contains--> app2
1071+ NewRelationship (t , & suite , tenantNode , subscriptionNode , graphAzure .Contains )
1072+ NewRelationship (t , & suite , subscriptionNode , app1 , graphAzure .Contains )
1073+ NewRelationship (t , & suite , subscriptionNode , app2 , graphAzure .Contains )
1074+
1075+ err := suite .GraphDB .ReadTransaction (suite .Context , func (tx graph.Transaction ) error {
1076+ paths , err := azure .FetchEntityDescendentPaths (tx , tenantNode , graphAzure .App )
1077+ require .NoError (t , err )
1078+
1079+ nodeIDs := paths .AllNodes ().IDs ()
1080+ require .Contains (t , nodeIDs , tenantNode .ID )
1081+ require .Contains (t , nodeIDs , subscriptionNode .ID )
1082+ require .Contains (t , nodeIDs , app1 .ID )
1083+ require .Contains (t , nodeIDs , app2 .ID )
1084+
1085+ return nil
1086+ })
1087+ require .NoError (t , err )
1088+ }
1089+
1090+ // TestFetchEntityDescendentPaths_TerminalNotConnectedToRoot verifies that a terminal node
1091+ // which exists in the same tenant but has no azure.Contains path leading back to root
1092+ // is excluded from the returned path set.
1093+ func TestFetchEntityDescendentPaths_TerminalNotConnectedToRoot (t * testing.T ) {
1094+ t .Parallel ()
1095+
1096+ suite := setupIntegrationTestSuite (t )
1097+ defer teardownIntegrationTestSuite (t , & suite )
1098+
1099+ var (
1100+ tenantID = integration .RandomObjectID (t )
1101+ tenantNode = NewAzureTenant (t , & suite , tenantID )
1102+ connectedApp = NewAzureApplication (t , & suite , "ConnectedApp" , integration .RandomObjectID (t ), tenantID )
1103+ disconnectedApp = NewAzureApplication (t , & suite , "DisconnectedApp" , integration .RandomObjectID (t ), tenantID )
1104+ )
1105+
1106+ // Only connectedApp has a Contains edge to root; disconnectedApp shares the tenantID
1107+ // but has no path leading back to tenantNode.
1108+ NewRelationship (t , & suite , tenantNode , connectedApp , graphAzure .Contains )
1109+
1110+ err := suite .GraphDB .ReadTransaction (suite .Context , func (tx graph.Transaction ) error {
1111+ paths , err := azure .FetchEntityDescendentPaths (tx , tenantNode , graphAzure .App )
1112+ require .NoError (t , err )
1113+
1114+ nodeIDs := paths .AllNodes ().IDs ()
1115+ require .Contains (t , nodeIDs , tenantNode .ID )
1116+ require .Contains (t , nodeIDs , connectedApp .ID )
1117+ require .NotContains (t , nodeIDs , disconnectedApp .ID )
1118+
1119+ return nil
1120+ })
1121+ require .NoError (t , err )
1122+ }
0 commit comments