diff --git a/core/src/test/java/com/google/adk/agents/BaseAgentFindSubAgentTest.java b/core/src/test/java/com/google/adk/agents/BaseAgentFindSubAgentTest.java new file mode 100644 index 000000000..f262c7d67 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/BaseAgentFindSubAgentTest.java @@ -0,0 +1,614 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test june-java-unit using AI Type AWS Bedrock Runtime AI and AI Model global.anthropic.claude-sonnet-4-6 + +ROOST_METHOD_HASH=findSubAgent_7b7e7c4829 +ROOST_METHOD_SIG_HASH=findSubAgent_3a41365e17 + +Scenario 1: Find a Direct Sub-Agent by Name + +Details: + TestName: findSubAgentReturnsDirectSubAgentWhenNameMatches + Description: Verifies that findSubAgent returns the correct direct child sub-agent when its name matches the search string. This tests the most basic happy-path scenario where the target agent exists at the first level of the sub-agent list. + +Execution: + Arrange: + - Create a concrete BaseAgent implementation named "child1" with no sub-agents. + - Create a concrete BaseAgent implementation named "child2" with no sub-agents. + - Create a root BaseAgent (named "root") with subAgents = [child1, child2]. + Act: + - Invoke root.findSubAgent("child1"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "child1". + +Validation: + This assertion confirms that the method correctly iterates through the subAgents list and returns the first agent whose name() matches the search string. It is critical to ensure that direct children are discoverable by name without requiring recursive traversal. + + +Scenario 2: Find a Deeply Nested Sub-Agent by Name + +Details: + TestName: findSubAgentReturnsNestedSubAgentWhenFoundAtDeeperLevel + Description: Verifies that findSubAgent recursively searches through the sub-agent tree and returns an agent that is not a direct child but is nested at a deeper level (grandchild or beyond). + +Execution: + Arrange: + - Create a concrete BaseAgent named "grandchild" with no sub-agents. + - Create a concrete BaseAgent named "child" with subAgents = [grandchild]. + - Create a root BaseAgent named "root" with subAgents = [child]. + Act: + - Invoke root.findSubAgent("grandchild"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "grandchild". + +Validation: + This assertion verifies that the recursive call subAgent.findSubAgent(name) is functioning correctly. The method must traverse multiple levels of the agent hierarchy to locate an agent that is not a direct descendant, which is central to the tree-traversal design of BaseAgent. + + +Scenario 3: Return Null When No Sub-Agent Matches the Given Name + +Details: + TestName: findSubAgentReturnsNullWhenNameDoesNotExistInTree + Description: Verifies that findSubAgent returns null when no agent in the entire sub-agent tree has a name matching the given search string. + +Execution: + Arrange: + - Create a concrete BaseAgent named "child1" with no sub-agents. + - Create a concrete BaseAgent named "child2" with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [child1, child2]. + Act: + - Invoke root.findSubAgent("nonExistentAgent"). + Assert: + - Assert that the returned value is null. + +Validation: + The null return is the specified contract when no matching agent is found. This test validates that the method correctly exhausts all branches of the sub-agent tree and returns null rather than throwing an exception or returning an incorrect agent. + + +Scenario 4: Return Null When the Agent Has No Sub-Agents (Empty List) + +Details: + TestName: findSubAgentReturnsNullWhenSubAgentListIsEmpty + Description: Verifies that findSubAgent returns null immediately when the agent has an empty sub-agents list, as there are no agents to search through. + +Execution: + Arrange: + - Create a concrete BaseAgent named "root" with subAgents = [] (empty list or null, relying on the constructor's null-to-empty-list conversion). + Act: + - Invoke root.findSubAgent("anyName"). + Assert: + - Assert that the returned value is null. + +Validation: + This edge case confirms that the for-each loop over an empty subAgents list is handled gracefully, with the method returning null as expected. It ensures robustness when dealing with leaf nodes or root agents that have no children. + + +Scenario 5: Find the First Matching Sub-Agent When Multiple Sub-Agents Share the Same Name + +Details: + TestName: findSubAgentReturnsFirstMatchWhenMultipleSubAgentsHaveSameName + Description: Verifies that findSubAgent returns the first sub-agent encountered in the list whose name matches, maintaining the iteration order of the subAgents list. While unique names are expected per the contract, this tests the method's behavior under this condition. + +Execution: + Arrange: + - Create a concrete BaseAgent named "duplicate" (instance A) with no sub-agents. + - Create a concrete BaseAgent named "duplicate" (instance B) with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [instanceA, instanceB]. + Act: + - Invoke root.findSubAgent("duplicate"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent is the same object reference as instanceA. + +Validation: + This confirms that the method returns on the first match found in iteration order. Although unique names are a stated requirement within the agent tree, this test documents the deterministic behavior of the method and confirms it does not scan beyond the first match. + + +Scenario 6: Find a Sub-Agent That Is a Direct Child When Its Name Matches but Other Siblings Do Not + +Details: + TestName: findSubAgentReturnsCorrectSubAgentAmongMultipleSiblings + Description: Verifies that findSubAgent correctly identifies and returns the matching sub-agent from a list of multiple siblings where only one matches the given name. + +Execution: + Arrange: + - Create concrete BaseAgent instances: "alpha", "beta", "gamma", each with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [alpha, beta, gamma]. + Act: + - Invoke root.findSubAgent("gamma"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "gamma". + +Validation: + This confirms that the iteration correctly loops past non-matching agents and finds the correct one at the end of the list. It validates that the method does not short-circuit incorrectly for agents listed later in the subAgents collection. + + +Scenario 7: Find a Sub-Agent at a Third Level of Nesting (Deep Recursion) + +Details: + TestName: findSubAgentReturnsAgentAtThirdNestingLevel + Description: Verifies that findSubAgent can locate an agent that is nested three levels deep (great-grandchild), demonstrating that the recursion works correctly for arbitrary depths. + +Execution: + Arrange: + - Create a concrete BaseAgent named "greatGrandchild" with no sub-agents. + - Create a concrete BaseAgent named "grandchild" with subAgents = [greatGrandchild]. + - Create a concrete BaseAgent named "child" with subAgents = [grandchild]. + - Create a root BaseAgent named "root" with subAgents = [child]. + Act: + - Invoke root.findSubAgent("greatGrandchild"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "greatGrandchild". + +Validation: + This validates that the recursive search correctly propagates down through multiple levels of the agent tree. The depth of the recursion is not artificially limited, which is important for supporting complex agent hierarchies in the application. + + +Scenario 8: findSubAgent Does Not Return the Root Agent Itself Even If Name Matches + +Details: + TestName: findSubAgentDoesNotReturnRootAgentWhenRootNameMatchesSearchName + Description: Verifies that findSubAgent only searches through subAgents and does not consider the agent on which the method is called. This distinguishes it from findAgent, which checks the current agent's name as well. + +Execution: + Arrange: + - Create a concrete BaseAgent named "child" with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [child]. + Act: + - Invoke root.findSubAgent("root"). + Assert: + - Assert that the returned value is null. + +Validation: + This confirms the precise behavioral boundary between findSubAgent and findAgent. The findSubAgent method is exclusively for searching descendants, not the caller itself. This is important for ensuring correct delegation logic when traversing agent trees. + + +Scenario 9: findSubAgent Searches Across Multiple Branches to Find the Target + +Details: + TestName: findSubAgentSearchesAcrossMultipleBranchesAndReturnsCorrectAgent + Description: Verifies that when the agent tree has multiple branches and the target agent exists in a non-first branch, findSubAgent correctly traverses the first branch (finding nothing) and then proceeds to locate the agent in the subsequent branch. + +Execution: + Arrange: + - Create a concrete BaseAgent named "branchOneChild" with no sub-agents. + - Create a concrete BaseAgent named "branchTwoChild" with no sub-agents. + - Create a concrete BaseAgent named "branchOne" with subAgents = [branchOneChild]. + - Create a concrete BaseAgent named "branchTwo" with subAgents = [branchTwoChild]. + - Create a root BaseAgent named "root" with subAgents = [branchOne, branchTwo]. + Act: + - Invoke root.findSubAgent("branchTwoChild"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "branchTwoChild". + +Validation: + This verifies that the search correctly backtracks after exhausting one branch and continues to the next sibling. This is a critical behavior for depth-first search traversal across a tree with multiple children, ensuring that no branch is skipped. + + +Scenario 10: Find a Sub-Agent That Is an Intermediate Node (Has Its Own Sub-Agents) + +Details: + TestName: findSubAgentReturnsIntermediateNodeAgentWhenNameMatches + Description: Verifies that findSubAgent can return an intermediate agent (one that has its own sub-agents) when its name matches, not just leaf nodes. + +Execution: + Arrange: + - Create a concrete BaseAgent named "deepLeaf" with no sub-agents. + - Create a concrete BaseAgent named "intermediateAgent" with subAgents = [deepLeaf]. + - Create a root BaseAgent named "root" with subAgents = [intermediateAgent]. + Act: + - Invoke root.findSubAgent("intermediateAgent"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "intermediateAgent"). + - Assert that the returned BaseAgent's subAgents() list contains deepLeaf. + +Validation: + This confirms that the method is not limited to finding only leaf nodes. It must correctly identify and return any agent within the tree, including intermediate nodes that serve as parents to other agents. This is relevant for scenarios where an intermediary agent needs to be retrieved for delegation or inspection. + + +Scenario 11: findSubAgent With a Name That Is an Empty String + +Details: + TestName: findSubAgentReturnsNullWhenSearchNameIsEmptyString + Description: Verifies the behavior of findSubAgent when the provided search name is an empty string, assuming no agent is named with an empty string. + +Execution: + Arrange: + - Create a concrete BaseAgent named "child" with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [child]. + Act: + - Invoke root.findSubAgent(""). + Assert: + - Assert that the returned value is null. + +Validation: + This edge case validates that the method handles an empty string search gracefully by returning null when no agent has an empty name. It ensures no unexpected behavior (such as a NullPointerException from String.equals) occurs with this boundary input. + + +Scenario 12: findSubAgent Returns Correct Agent in Wide Tree (Many Direct Sub-Agents) + +Details: + TestName: findSubAgentReturnsCorrectAgentInWideTreeWithManyDirectSubAgents + Description: Verifies that findSubAgent correctly locates a target agent when the root agent has a large number of direct sub-agents (wide tree), ensuring the iteration is exhaustive. + +Execution: + Arrange: + - Create 10 concrete BaseAgent instances named "agent0" through "agent9", each with no sub-agents. + - Create a root BaseAgent named "root" with subAgents = [agent0, agent1, ..., agent9]. + Act: + - Invoke root.findSubAgent("agent9"). + Assert: + - Assert that the returned BaseAgent is not null. + - Assert that the returned BaseAgent's name() equals "agent9". + +Validation: + This confirms that the loop iterates over all sub-agents without stopping prematurely. It ensures correctness in wide, flat hierarchies where the target agent is the last element in the subAgents collection. + +*/ + +// ********RoostGPT******** +```java/* + * Copyright 2025 Google LLC + * + * 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 com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import com.google.adk.agents.Callbacks.AfterAgentCallback; +import com.google.adk.agents.Callbacks.BeforeAgentCallback; +import com.google.adk.events.Event; +import com.google.adk.plugins.PluginManager; +import com.google.common.collect.ImmutableList; +import com.google.genai.types.Content; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Maybe; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; +import static com.google.common.collect.ImmutableList.toImmutableList; +import com.google.adk.Telemetry; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.DoNotCall; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.reactivex.rxjava3.core.Single; +import java.util.function.Function; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + +class BaseAgentFindSubAgentTest { + + // Concrete implementation of BaseAgent for testing purposes + static class TestBaseAgent extends BaseAgent { + + public TestBaseAgent(String name, List subAgents) { + super(name, "description for " + name, subAgents, null, null); + } + + @Override + protected Flowable runAsyncImpl(InvocationContext invocationContext) { + return Flowable.empty(); + } + + @Override + protected Maybe runLiveImpl(InvocationContext invocationContext) { + return Maybe.empty(); + } + + } + + // ----------------------------------------------------------------------- + // Scenario 1: Find a Direct Sub-Agent by Name + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsDirectSubAgentWhenNameMatches() { + TestBaseAgent child1 = new TestBaseAgent("child1", null); + TestBaseAgent child2 = new TestBaseAgent("child2", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child1, child2)); + BaseAgent result = root.findSubAgent("child1"); + assertNotNull(result); + assertEquals("child1", result.name()); + } + + // ----------------------------------------------------------------------- + // Scenario 2: Find a Deeply Nested Sub-Agent by Name + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsNestedSubAgentWhenFoundAtDeeperLevel() { + TestBaseAgent grandchild = new TestBaseAgent("grandchild", null); + TestBaseAgent child = new TestBaseAgent("child", ImmutableList.of(grandchild)); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child)); + BaseAgent result = root.findSubAgent("grandchild"); + assertNotNull(result); + assertEquals("grandchild", result.name()); + } + + // ----------------------------------------------------------------------- + // Scenario 3: Return Null When No Sub-Agent Matches the Given Name + // ----------------------------------------------------------------------- + @Test + @Tag("invalid") + void findSubAgentReturnsNullWhenNameDoesNotExistInTree() { + TestBaseAgent child1 = new TestBaseAgent("child1", null); + TestBaseAgent child2 = new TestBaseAgent("child2", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child1, child2)); + BaseAgent result = root.findSubAgent("nonExistentAgent"); + assertNull(result); + } + + // ----------------------------------------------------------------------- + // Scenario 4: Return Null When the Agent Has No Sub-Agents (Empty List) + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void findSubAgentReturnsNullWhenSubAgentListIsEmpty() { + TestBaseAgent root = new TestBaseAgent("root", null); + BaseAgent result = root.findSubAgent("anyName"); + assertNull(result); + } + + // ----------------------------------------------------------------------- + // Scenario 5: Find the First Matching Sub-Agent When Multiple Share Name + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsFirstMatchWhenMultipleSubAgentsHaveSameName() { + TestBaseAgent instanceA = new TestBaseAgent("duplicate", null); + TestBaseAgent instanceB = new TestBaseAgent("duplicate", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(instanceA, instanceB)); + BaseAgent result = root.findSubAgent("duplicate"); + assertNotNull(result); + assertSame(instanceA, result); + } + + // ----------------------------------------------------------------------- + // Scenario 6: Find Correct Sub-Agent Among Multiple Siblings + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsCorrectSubAgentAmongMultipleSiblings() { + TestBaseAgent alpha = new TestBaseAgent("alpha", null); + TestBaseAgent beta = new TestBaseAgent("beta", null); + TestBaseAgent gamma = new TestBaseAgent("gamma", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(alpha, beta, gamma)); + BaseAgent result = root.findSubAgent("gamma"); + assertNotNull(result); + assertEquals("gamma", result.name()); + } + + // ----------------------------------------------------------------------- + // Scenario 7: Find a Sub-Agent at Third Level of Nesting (Deep Recursion) + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsAgentAtThirdNestingLevel() { + TestBaseAgent greatGrandchild = new TestBaseAgent("greatGrandchild", null); + TestBaseAgent grandchild = new TestBaseAgent("grandchild", ImmutableList.of(greatGrandchild)); + TestBaseAgent child = new TestBaseAgent("child", ImmutableList.of(grandchild)); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child)); + BaseAgent result = root.findSubAgent("greatGrandchild"); + assertNotNull(result); + assertEquals("greatGrandchild", result.name()); + } + + // ----------------------------------------------------------------------- + // Scenario 8: findSubAgent Does Not Return Root Agent Even If Name Matches + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void findSubAgentDoesNotReturnRootAgentWhenRootNameMatchesSearchName() { + TestBaseAgent child = new TestBaseAgent("child", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child)); + BaseAgent result = root.findSubAgent("root"); + assertNull(result); + } + + // ----------------------------------------------------------------------- + // Scenario 9: findSubAgent Searches Across Multiple Branches + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentSearchesAcrossMultipleBranchesAndReturnsCorrectAgent() { + TestBaseAgent branchOneChild = new TestBaseAgent("branchOneChild", null); + TestBaseAgent branchTwoChild = new TestBaseAgent("branchTwoChild", null); + TestBaseAgent branchOne = new TestBaseAgent("branchOne", ImmutableList.of(branchOneChild)); + TestBaseAgent branchTwo = new TestBaseAgent("branchTwo", ImmutableList.of(branchTwoChild)); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(branchOne, branchTwo)); + BaseAgent result = root.findSubAgent("branchTwoChild"); + assertNotNull(result); + assertEquals("branchTwoChild", result.name()); + } + + // ----------------------------------------------------------------------- + // Scenario 10: Find an Intermediate Node Agent (Has Its Own Sub-Agents) + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsIntermediateNodeAgentWhenNameMatches() { + TestBaseAgent deepLeaf = new TestBaseAgent("deepLeaf", null); + TestBaseAgent intermediateAgent = new TestBaseAgent("intermediateAgent", ImmutableList.of(deepLeaf)); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(intermediateAgent)); + BaseAgent result = root.findSubAgent("intermediateAgent"); + assertNotNull(result); + assertEquals("intermediateAgent", result.name()); + assertFalse(result.subAgents().isEmpty()); + assertEquals("deepLeaf", result.subAgents().get(0).name()); + } + + // ----------------------------------------------------------------------- + // Scenario 11: findSubAgent With a Name That Is an Empty String + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void findSubAgentReturnsNullWhenSearchNameIsEmptyString() { + TestBaseAgent child = new TestBaseAgent("child", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child)); + BaseAgent result = root.findSubAgent(""); + assertNull(result); + } + + // ----------------------------------------------------------------------- + // Scenario 12: findSubAgent Returns Correct Agent in Wide Tree + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsCorrectAgentInWideTreeWithManyDirectSubAgents() { + List subAgents = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + subAgents.add(new TestBaseAgent("agent" + i, null)); + } + TestBaseAgent root = new TestBaseAgent("root", subAgents); + BaseAgent result = root.findSubAgent("agent9"); + assertNotNull(result); + assertEquals("agent9", result.name()); + } + + // ----------------------------------------------------------------------- + // Additional: integration test - complex multi-level, multi-branch tree + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void findSubAgentWorksCorrectlyInComplexMultiLevelMultiBranchTree() { + TestBaseAgent leaf1 = new TestBaseAgent("leaf1", null); + TestBaseAgent leaf2 = new TestBaseAgent("leaf2", null); + TestBaseAgent leaf3 = new TestBaseAgent("leaf3", null); + TestBaseAgent leaf4 = new TestBaseAgent("leaf4", null); + TestBaseAgent mid1 = new TestBaseAgent("mid1", ImmutableList.of(leaf1, leaf2)); + TestBaseAgent mid2 = new TestBaseAgent("mid2", ImmutableList.of(leaf3, leaf4)); + TestBaseAgent top1 = new TestBaseAgent("top1", ImmutableList.of(mid1)); + TestBaseAgent top2 = new TestBaseAgent("top2", ImmutableList.of(mid2)); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(top1, top2)); + assertNotNull(root.findSubAgent("top1")); + assertEquals("top1", root.findSubAgent("top1").name()); + assertNotNull(root.findSubAgent("mid2")); + assertEquals("mid2", root.findSubAgent("mid2").name()); + assertNotNull(root.findSubAgent("leaf4")); + assertEquals("leaf4", root.findSubAgent("leaf4").name()); + assertNull(root.findSubAgent("root")); + assertNull(root.findSubAgent("unknown")); + } + + // ----------------------------------------------------------------------- + // Additional: boundary - single sub-agent matches exactly + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void findSubAgentReturnsSingleSubAgentWhenItIsTheOnlyOneAndNameMatches() { + TestBaseAgent onlyChild = new TestBaseAgent("onlyChild", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(onlyChild)); + BaseAgent result = root.findSubAgent("onlyChild"); + assertNotNull(result); + assertSame(onlyChild, result); + } + + // ----------------------------------------------------------------------- + // Additional: boundary - single sub-agent does not match + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void findSubAgentReturnsNullWhenSingleSubAgentDoesNotMatch() { + TestBaseAgent onlyChild = new TestBaseAgent("onlyChild", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(onlyChild)); + BaseAgent result = root.findSubAgent("notOnlyChild"); + assertNull(result); + } + + // ----------------------------------------------------------------------- + // Additional: valid - second direct child matches, first does not + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsSecondChildWhenFirstChildDoesNotMatch() { + TestBaseAgent firstChild = new TestBaseAgent("firstChild", null); + TestBaseAgent secondChild = new TestBaseAgent("secondChild", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(firstChild, secondChild)); + BaseAgent result = root.findSubAgent("secondChild"); + assertNotNull(result); + assertEquals("secondChild", result.name()); + assertSame(secondChild, result); + } + + // ----------------------------------------------------------------------- + // Additional: valid - verify object identity (same instance returned) + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void findSubAgentReturnsSameInstanceAsOriginalChildAgent() { + TestBaseAgent child = new TestBaseAgent("child", null); + TestBaseAgent root = new TestBaseAgent("root", ImmutableList.of(child)); + BaseAgent result = root.findSubAgent("child"); + assertSame(child, result); + } + + // ----------------------------------------------------------------------- + // Additional: integration - beforeAgentCallback & afterAgentCallback + // initialized correctly while tree traversal is unaffected + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void findSubAgentWorksCorrectlyWhenAgentsHaveCallbacksConfigured() { + BeforeAgentCallback beforeCallback = callbackContext -> Maybe.empty(); + AfterAgentCallback afterCallback = callbackContext -> Maybe.empty(); + BaseAgent childWithCallbacks = new BaseAgent( + "childWithCallbacks", + "child description", + null, + null, + null) { + @Override + protected Flowable runAsyncImpl(InvocationContext invocationContext) { + return Flowable.empty(); + } + @Override + protected Maybe runLiveImpl(InvocationContext invocationContext) { + return Maybe.empty(); + } + }; + BaseAgent rootWithCallbacks = new BaseAgent( + "rootWithCallbacks", + "root description", + ImmutableList.of(childWithCallbacks), + ImmutableList.of(beforeCallback), + ImmutableList.of(afterCallback)) { + @Override + protected Flowable runAsyncImpl(InvocationContext invocationContext) { + return Flowable.empty(); + } \ No newline at end of file